संकलक अस्पष्ट आमंत्रण त्रुटि - फंक <> या क्रिया के साथ अनाम विधि और विधि समूह


102

मेरे पास एक परिदृश्य है जहां मैं फ़ंक्शन को कॉल करने के लिए अनाम विधियों (या लंबोदर सिंटैक्स) के बजाय विधि समूह सिंटैक्स का उपयोग करना चाहता हूं।

फ़ंक्शन में दो अधिभार हैं, एक जो एक लेता है Action, दूसरा एक लेता है Func<string>

मैं गुमनाम तरीके से (या लैम्ब्डा सिंटैक्स) का उपयोग करके दो ओवरलोड्स को खुशी से कह सकता हूं, लेकिन अगर मैं समूह समूह सिंटैक्स का उपयोग करता हूं, तो एंबिगलेस मंगलाचरण की एक संकलक त्रुटि प्राप्त करें। मैं Actionया तो स्पष्ट कास्टिंग द्वारा समाधान कर सकता हूं Func<string>, लेकिन यह मत सोचो कि यह आवश्यक होना चाहिए।

क्या कोई समझा सकता है कि स्पष्ट जातियों की आवश्यकता क्यों होनी चाहिए।

नीचे दिए गए कोड का नमूना।

class Program
{
    static void Main(string[] args)
    {
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();

        // These both compile (lambda syntax)
        classWithDelegateMethods.Method(() => classWithSimpleMethods.GetString());
        classWithDelegateMethods.Method(() => classWithSimpleMethods.DoNothing());

        // These also compile (method group with explicit cast)
        classWithDelegateMethods.Method((Func<string>)classWithSimpleMethods.GetString);
        classWithDelegateMethods.Method((Action)classWithSimpleMethods.DoNothing);

        // These both error with "Ambiguous invocation" (method group)
        classWithDelegateMethods.Method(classWithSimpleMethods.GetString);
        classWithDelegateMethods.Method(classWithSimpleMethods.DoNothing);
    }
}

class ClassWithDelegateMethods
{
    public void Method(Func<string> func) { /* do something */ }
    public void Method(Action action) { /* do something */ }
}

class ClassWithSimpleMethods
{
    public string GetString() { return ""; }
    public void DoNothing() { }
}

सी # 7.3 अपडेट

के रूप में प्रति 0xcde 20 मार्च 2019 पर नीचे की टिप्पणी (नौ साल के बाद मैं इस प्रश्न पोस्ट!), सी # करने के लिए 7.3 धन्यवाद के रूप में इस कोड compiles बेहतर अधिभार उम्मीदवारों


मैंने आपका कोड आज़मा लिया है और मुझे एक अतिरिक्त संकलन समय त्रुटि मिल रही है: 'void test.ClassWithSimpleMethods.DoNothing ()' में गलत रिटर्न प्रकार है (जो लाइन 25 पर है, जहां अस्पष्टता त्रुटि है)
मैट एलेन

@ मैट: मैं उस त्रुटि को भी देखता हूं। मैंने अपनी पोस्ट में जिन त्रुटियों का हवाला दिया, वे संकलन के मुद्दे थे जो कि वी.एस. पर प्रकाश डालते हैं, इससे पहले कि आप एक पूर्ण संकलन की कोशिश करें।
रिचर्ड ईव

1
वैसे, यह एक बड़ा सवाल था। मुझे कुछ भी पसंद है जो मुझे ऐनक में मजबूर कर देता है :)
जॉन स्कीट

1
ध्यान दें कि आपका नमूना कोड संकलित करेगा यदि आप C # 7.3 ( <LangVersion>7.3</LangVersion>) या बाद में बेहतर अधिभार उम्मीदवारों के लिए धन्यवाद का उपयोग करते हैं ।
0xced

जवाबों:


97

सबसे पहले, मुझे सिर्फ यह कहना चाहिए कि जॉन का जवाब सही है। यह कल्पना के सबसे बालों वाले हिस्सों में से एक है, जो पहले सिर में गोता लगाने के लिए जॉन पर अच्छा था।

दूसरा, मुझे यह कहने दें कि यह पंक्ति:

एक अंतर्निहित समूह एक विधि समूह से एक संगत प्रतिनिधि प्रकार में मौजूद है

(जोर दिया गया) गहरा भ्रामक और दुर्भाग्यपूर्ण है। मैं यहाँ "संगत" शब्द को हटाने के बारे में मैड्स से बात करूंगा।

इसका कारण भ्रामक और दुर्भाग्यपूर्ण है क्योंकि ऐसा लग रहा है कि यह खंड 15.2, "प्रतिनिधि संगतता" कह रहा है। धारा 15.2 में विधियों और प्रतिनिधि प्रकारों के बीच संगतता संबंध का वर्णन किया गया है , लेकिन यह विधि समूहों और प्रतिनिधि प्रकारों की परिवर्तनीयता का प्रश्न है , जो अलग है।

अब जब हमें वह मिल गया है, तो हम कल्पना के खंड 6.6 से चल सकते हैं और देख सकते हैं कि हमें क्या मिलता है।

ओवरलोड रिज़ॉल्यूशन करने के लिए हमें पहले यह निर्धारित करना होगा कि कौन से ओवरलोड लागू उम्मीदवार हैं । यदि सभी तर्क औपचारिक पैरामीटर प्रकारों के लिए परिवर्तनीय हैं, तो एक उम्मीदवार लागू होता है। अपने कार्यक्रम के इस सरलीकृत संस्करण पर विचार करें:

class Program
{
    delegate void D1();
    delegate string D2();
    static string X() { return null; }
    static void Y(D1 d1) {}
    static void Y(D2 d2) {}
    static void Main()
    {
        Y(X);
    }
}

तो चलो लाइन के माध्यम से लाइन के माध्यम से चलते हैं।

एक अंतर्निहित समूह एक विधि समूह से एक संगत प्रतिनिधि प्रकार में मौजूद है।

मैंने पहले ही चर्चा की है कि "संगत" शब्द यहाँ कैसे दुर्भाग्यपूर्ण है। आगे बढ़ते रहना। Y (X) पर ओवरलोड रिज़ॉल्यूशन करते समय हम सोच रहे हैं कि क्या विधि समूह X D1 में परिवर्तित होता है? क्या यह D2 में परिवर्तित होता है?

एक प्रतिनिधि प्रकार डी और एक अभिव्यक्ति ई को देखते हुए जिसे एक विधि समूह के रूप में वर्गीकृत किया गया है, एक अंतर्निहित रूपांतरण ई से डी तक मौजूद है यदि ई में कम से कम एक विधि है जो लागू होता है [...] पैरामीटर के उपयोग द्वारा निर्मित तर्क सूची के लिए। डी के प्रकार और संशोधन, जैसा कि निम्नलिखित में वर्णित है।

अब तक सब ठीक है। X में D1 या D2 की तर्क सूचियों के साथ लागू एक विधि हो सकती है।

विधि समूह E से किसी प्रतिनिधि प्रकार D में रूपांतरण का संकलन-समय अनुप्रयोग निम्नलिखित में वर्णित है।

यह लाइन वास्तव में कुछ भी दिलचस्प नहीं कहती है।

ध्यान दें कि E से D तक एक निहित रूपांतरण का अस्तित्व इस बात की गारंटी नहीं देता है कि रूपांतरण का संकलन-समय अनुप्रयोग त्रुटि के बिना सफल होगा।

यह रेखा आकर्षक है। इसका मतलब है कि निहित रूपांतरण हैं जो मौजूद हैं, लेकिन जो त्रुटियों में बदल रहे हैं! यह C # का एक विचित्र नियम है। एक पल को खोदने के लिए, यहाँ एक उदाहरण है:

void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));

एक एक्सप्रेशन ऑपरेशन एक अभिव्यक्ति ट्री में अवैध है। हालाँकि, लैम्बडा अभी भी अभिव्यक्ति ट्री प्रकार के लिए परिवर्तनीय है, भले ही रूपांतरण कभी भी उपयोग किया जाता है, यह एक त्रुटि है! यहाँ सिद्धांत यह है कि हम बाद में एक अभिव्यक्ति के पेड़ में क्या जा सकते हैं के नियमों को बदलना चाहते हैं; उन नियमों को बदलने से सिस्टम के प्रकार के नियम नहीं बदलने चाहिए । हम आपको अपने कार्यक्रमों को अब अस्पष्ट बनाने के लिए मजबूर करना चाहते हैं , ताकि जब हम भविष्य में अभिव्यक्ति के पेड़ों के नियमों को बदलकर उन्हें बेहतर बना सकें, तो हम अधिभार संकल्प में टूटने वाले बदलावों का परिचय नहीं देते हैं

वैसे भी, यह इस तरह के विचित्र नियम का एक और उदाहरण है। ओवरलोड रिज़ॉल्यूशन के उद्देश्यों के लिए रूपांतरण मौजूद हो सकता है, लेकिन वास्तव में उपयोग करने के लिए एक त्रुटि हो सकती है। हालांकि वास्तव में, यह बिल्कुल वैसी स्थिति नहीं है जैसी हम यहां हैं।

आगे बढ़ते रहना:

E (A) [A] [...] के तर्क विधि सूची के अनुरूप एक एकल विधि M का चयन किया जाता है। तर्क सूची A एक अभिव्यक्ति की एक सूची है, जिसे प्रत्येक को चर के रूप में वर्गीकृत किया गया है [...] -पार्टी-डी की सूची।

ठीक है। इसलिए हम डी 1 के संबंध में एक्स पर अधिभार संकल्प करते हैं। डी 1 की औपचारिक पैरामीटर सूची खाली है, इसलिए हम एक्स () और आनंद पर अधिभार संकल्प करते हैं, हम एक विधि "स्ट्रिंग एक्स ()" पाते हैं जो काम करता है। इसी तरह, डी 2 की औपचारिक पैरामीटर सूची खाली है। फिर, हम पाते हैं कि "स्ट्रिंग एक्स ()" एक विधि है जो यहां भी काम करती है।

यहाँ सिद्धांत यह है कि निर्धारण विधि समूह परिवर्तनीयता को अधिभार संकल्प का उपयोग करते हुए विधि समूह से एक विधि का चयन करने की आवश्यकता होती है , और अधिभार संकल्प विभिन्न प्रकारों पर विचार नहीं करता है

यदि एल्गोरिथ्म [...] एक त्रुटि पैदा करता है, तो एक संकलन-समय त्रुटि उत्पन्न होती है। अन्यथा एल्गोरिथ्म डी के रूप में मापदंडों की एक ही संख्या होने वाले एक सबसे अच्छी विधि एम का उत्पादन करता है और रूपांतरण मौजूद माना जाता है।

विधि समूह एक्स में केवल एक ही विधि है, इसलिए यह सबसे अच्छा होना चाहिए। हम सफलतापूर्वक साबित कर दिया है कि एक रूपांतरण मौजूद एक्स से डी 1 के लिए और एक्स से डी 2 के लिए।

अब, क्या यह पंक्ति प्रासंगिक है?

चयनित विधि M को प्रतिनिधि प्रकार D के साथ संगत होना चाहिए, या अन्यथा, एक संकलन-समय त्रुटि उत्पन्न होती है।

दरअसल, नहीं, इस कार्यक्रम में नहीं। हम कभी भी इस रेखा को सक्रिय नहीं कर पाए। क्योंकि, याद रखें, हम यहाँ क्या कर रहे हैं Y (X) पर अधिभार संकल्प करने की कोशिश कर रहे हैं। हमारे पास दो उम्मीदवार Y (D1) और Y (D2) हैं। दोनों लागू हैं। कौन सा बेहतर है ? विनिर्देश में कहीं भी हम इन दो संभावित रूपांतरणों के बीच विश्वासघात का वर्णन नहीं करते हैं

अब, कोई निश्चित रूप से यह तर्क दे सकता है कि एक वैध रूपांतरण एक त्रुटि पैदा करने वाले से बेहतर है। इसके बाद प्रभावी रूप से कहा जा सकता है, इस मामले में, कि अधिभार संकल्प रिटर्न प्रकारों पर विचार करता है, जो कि हम से बचना चाहते हैं। फिर सवाल यह है कि कौन सा सिद्धांत बेहतर है: (1) अपरिवर्तनीय बनाए रखें कि अधिभार संकल्प रिटर्न प्रकारों पर विचार नहीं करता है, या (2) एक रूपांतरण लेने की कोशिश करता है जिसे हम जानते हैं कि हम जो नहीं जानते हैं उस पर काम करेंगे?

यह एक निर्णय कॉल है। साथ lambdas , हम करते हैं , रूपांतरण के इन प्रकार में वापसी प्रकार पर विचार खंड 7.4.3.3 में:

E एक अनाम फ़ंक्शन है, T1 और T2 समान पैरामीटर सूचियों के साथ प्रतिनिधि प्रकार या अभिव्यक्ति ट्री प्रकार हैं, एक अनुमानित वापसी प्रकार X उस पैरामीटर सूची के संदर्भ में ई के लिए मौजूद है, और निम्न में से एक है:

  • T1 में वापसी प्रकार Y1 है, और T2 में वापसी प्रकार Y2 है, और X से Y1 में रूपांतरण X से Y2 में रूपांतरण से बेहतर है

  • T1 में एक वापसी प्रकार Y है, और T2 वापस लौटने वाला है

यह दुर्भाग्यपूर्ण है कि विधि समूह रूपांतरण और लंबोदर रूपांतरण इस संबंध में असंगत हैं। हालांकि, मैं इसके साथ रह सकता हूं।

वैसे भी, हमारे पास यह निर्धारित करने के लिए कोई "शर्त" नहीं है कि कौन सा रूपांतरण बेहतर है, X से D1 या X से D2। इसलिए हम वाई (एक्स) के संकल्प पर एक अस्पष्टता त्रुटि देते हैं।


8
क्रैकिंग - दोनों उत्तर के लिए बहुत धन्यवाद और (उम्मीद है) कल्पना में परिणाम में सुधार :) व्यक्तिगत रूप से मुझे लगता है कि व्यवहार को अधिक सहज बनाने के लिए यह ओवरलोड रिज़ॉल्यूशन के लिए विधि समूह रूपांतरणों के लिए खाते में वापसी के लिए उचित होगा , लेकिन मुझे समझ में आता है कि यह निरंतरता की कीमत पर ऐसा करेगा। (वही कहा जा सकता है जेनेरिक प्रकार का अनुमान, जो विधि समूह रूपांतरणों पर लागू होता है, जब विधि समूह में केवल एक ही विधि होती है, जैसा कि मुझे लगता है कि हमने पहले चर्चा की है।)
जॉन स्कीट

35

संपादित करें: मुझे लगता है कि मुझे मिल गया है।

Zinglon कहते हैं, उसे वहाँ से एक अंतर्निहित रूपांतरण है, क्योंकि है GetStringकरने के लिए Actionभले ही संकलन समय आवेदन विफल हो जाएगा। यहां खंड 6.6 का परिचय कुछ जोर (मेरा) के साथ दिया गया है:

एक अंतर्निहित रूपांतरण (§6.1) एक विधि समूह ()7.1) से एक संगत प्रतिनिधि प्रकार में मौजूद है। एक प्रतिनिधि प्रकार डी और एक अभिव्यक्ति ई को दिया जाता है जिसे एक विधि समूह के रूप में वर्गीकृत किया गया है, एक अंतर्निहित रूपांतरण ई से डी तक मौजूद है यदि ई में कम से कम एक विधि शामिल है जो एक तर्क सूची के निर्माण के लिए अपने सामान्य रूप (.47.4.3.1) में लागू होती है। निम्नानुसार वर्णित डी के पैरामीटर प्रकार और संशोधक के उपयोग से

अब, मैं पहले वाक्य से भ्रमित हो रहा था - जो एक संगत प्रतिनिधि प्रकार में रूपांतरण की बात करता है। Actionमें किसी भी विधि के लिए एक संगत प्रतिनिधि नहीं है GetStringविधि समूह है, लेकिन GetString()विधि है एक तर्क सूची पैरामीटर प्रकार और डी नोट के संशोधक के उपयोग के द्वारा निर्माण किया है कि इस के लिए अपने सामान्य रूप में लागू नहीं होता है की वापसी प्रकार के बारे में बात डी। इसलिए यह भ्रमित हो रहा है ... क्योंकि यह केवल रूपांतरण को लागू करते GetString()समय प्रतिनिधि की संगतता के लिए जांच करेगा, इसके अस्तित्व की जांच नहीं।

मुझे लगता है कि समीकरण से ओवरलोडिंग को संक्षेप में छोड़ने का निर्देश है, और देखें कि रूपांतरण के अस्तित्व और इसकी प्रयोज्यता के बीच यह अंतर कैसे प्रकट हो सकता है। यहाँ एक छोटा लेकिन पूर्ण उदाहरण है:

using System;

class Program
{
    static void ActionMethod(Action action) {}
    static void IntMethod(int x) {}

    static string GetString() { return ""; }

    static void Main(string[] args)
    {
        IntMethod(GetString);
        ActionMethod(GetString);
    }
}

Mainसंकलन में न तो विधि मंगलाचरण के भाव , बल्कि त्रुटि संदेश अलग हैं। यहाँ एक के लिए है IntMethod(GetString):

Test.cs (12,9): त्रुटि CS1502: 'Program.IntMethod (int)' के लिए सबसे अच्छा अतिभारित विधि मिलान में कुछ अमान्य तर्क हैं

दूसरे शब्दों में, कल्पना का खंड 7.4.3.1 किसी भी लागू फ़ंक्शन सदस्यों को नहीं मिल सकता है।

अब यहाँ के लिए त्रुटि है ActionMethod(GetString):

Test.cs (13,22): CS0407 त्रुटि: 'string Program.GetString ()' में गलत रिटर्न प्रकार है

इस बार यह उस विधि से काम किया है जिसे वह कॉल करना चाहता है - लेकिन यह आवश्यक रूपांतरण करने में विफल है। दुर्भाग्य से मुझे उस युक्ति का थोड़ा भी पता नहीं चल पा रहा है जहाँ वह अंतिम जाँच की जाती है - ऐसा लगता है कि यह 7.5.5.1 में हो सकता है, लेकिन मैं बिल्कुल नहीं देख सकता।


पुराने उत्तर को हटा दिया गया, इस बिट को छोड़कर - क्योंकि मुझे उम्मीद है कि एरिक इस प्रश्न के "क्यों" पर प्रकाश डाल सकता है ...

अभी भी देख रहे हैं ... मतलब समय में, अगर हम तीन बार "एरिक लिपर्ट" कहते हैं, तो क्या आपको लगता है कि हम एक यात्रा करेंगे (और इस तरह एक उत्तर)?


@ जॉन - क्या ऐसा हो सकता है classWithSimpleMethods.GetStringऔर classWithSimpleMethods.DoNothingप्रतिनिधि नहीं हैं?
डैनियल ए। व्हाइट

@Daniel: नहीं - वे अभिव्यक्तियाँ विधि समूह अभिव्यक्तियाँ हैं, और ओवरलोड विधियों को केवल तभी लागू किया जाना चाहिए जब विधि समूह से प्रासंगिक पैरामीटर प्रकार में निहित रूपांतरण हो। युक्ति का अनुभाग 7.4.3.1 देखें।
जॉन स्कीट

खंड 6.6 को पढ़ना, ऐसा लगता है कि classWithSimpleMethods.GetString से एक्शन को रूपांतरण माना जाता है क्योंकि पैरामीटर सूचियां संगत हैं, लेकिन संकलन समय पर विफल हो जाता है (यदि प्रयास किया गया)। इसलिए, एक अंतर्निहित रूपांतरण करता है दोनों प्रतिनिधि प्रकार के लिए मौजूद हैं और कॉल अस्पष्ट है।
ज़िंगलोन

@zinglon: आप कैसे पढ़ रहे हैं §6.6 निर्धारित करने के लिए कि से एक रूपांतरण ClassWithSimpleMethods.GetStringके लिए Actionमान्य है? Mएक प्रतिनिधि प्रकार D()15.2) के साथ संगत होने की विधि के लिए "वापसी के प्रकार से एक पहचान या निहित संदर्भ रूपांतरण मौजूद Mहै D।"
जसों

@ जेसन: युक्ति यह नहीं कहती कि रूपांतरण वैध है, यह कहता है कि यह मौजूद है । वास्तव में, यह अमान्य है क्योंकि यह संकलन समय पर विफल हो जाता है। Determine6.6 के पहले दो बिंदु निर्धारित करते हैं कि क्या रूपांतरण मौजूद है। निम्नलिखित बिंदु निर्धारित करते हैं कि क्या रूपांतरण सफल होगा। बिंदु 2 से: "अन्यथा एल्गोरिथ्म डी के रूप में मापदंडों की समान संख्या वाले एक एकल सर्वोत्तम विधि एम पैदा करता है और रूपांतरण को माना जाता है।" §15.2 बिंदु 3 में लगाया जाता है
ज़िंगलोन

1

का उपयोग करते हुए Func<string>और Action<string>(जाहिर है बहुत करने के लिए विभिन्न Actionऔर Func<string>में) ClassWithDelegateMethodsको हटा अस्पष्टता।

अस्पष्टता भी बीच में होती है Actionऔर Func<int>

मुझे इसके साथ अस्पष्टता त्रुटि भी मिलती है:

class Program
{ 
    static void Main(string[] args) 
    { 
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods(); 
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods(); 

        classWithDelegateMethods.Method(classWithSimpleMethods.GetOne);
    } 
} 

class ClassWithDelegateMethods 
{ 
    public void Method(Func<int> func) { /* do something */ }
    public void Method(Func<string> func) { /* do something */ } 
}

class ClassWithSimpleMethods 
{ 
    public string GetString() { return ""; } 
    public int GetOne() { return 1; }
} 

आगे के प्रयोग से पता चलता है कि जब स्वयं द्वारा एक विधि समूह में पास किया जाता है, तो यह निर्धारित करते समय वापसी प्रकार को पूरी तरह से अनदेखा किया जाता है कि किस अधिभार का उपयोग करना है।

class Program
{
    static void Main(string[] args)
    {
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();

        //The call is ambiguous between the following methods or properties: 
        //'test.ClassWithDelegateMethods.Method(System.Func<int,int>)' 
        //and 'test.ClassWithDelegateMethods.Method(test.ClassWithDelegateMethods.aDelegate)'
        classWithDelegateMethods.Method(classWithSimpleMethods.GetX);
    }
}

class ClassWithDelegateMethods
{
    public delegate string aDelegate(int x);
    public void Method(Func<int> func) { /* do something */ }
    public void Method(Func<string> func) { /* do something */ }
    public void Method(Func<int, int> func) { /* do something */ }
    public void Method(Func<string, string> func) { /* do something */ }
    public void Method(aDelegate ad) { }
}

class ClassWithSimpleMethods
{
    public string GetString() { return ""; }
    public int GetOne() { return 1; }
    public string GetX(int x) { return x.ToString(); }
} 

0

के साथ ओवरलोडिंग Funcऔर Action(जैसे दोनों ही डेलिगेट हैं)

string Function() // Func<string>
{
}

void Function() // Action
{
}

यदि आप ध्यान दें, तो कंपाइलर को यह पता नहीं होता है कि किसे कॉल करना है क्योंकि वे केवल रिटर्न प्रकारों से भिन्न होते हैं।


मुझे नहीं लगता कि यह वास्तव में ऐसा है - क्योंकि आप एक Func<string>में परिवर्तित नहीं कर सकते हैं Action... और आप एक विधि समूह को केवल एक विधि से परिवर्तित नहीं कर सकते हैं जो एक स्ट्रिंग को Actionया तो लौटाता है ।
जॉन स्केट

2
आप एक प्रतिनिधि नहीं ला सकते हैं जिसका कोई पैरामीटर न हो और रिटर्न न stringहो Action। मैं नहीं देखता कि अस्पष्टता क्यों है।
जेसन

3
@dtb: हाँ, ओवरलोडिंग को हटाने से समस्या दूर हो जाती है - लेकिन यह वास्तव में नहीं समझाता है कि समस्या क्यों है।
जॉन स्कीट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.