Derleyici Belirsiz çağrı hatası - Func <> veya Action ile anonim yöntem ve yöntem grubu


102

Bir işlevi çağırmak için anonim yöntemler (veya lambda sözdizimi) yerine yöntem grubu sözdizimini kullanmak istediğim bir senaryom var.

Fonksiyonun iki aşırı yüklemesi vardır, biri an alır Action, diğeri a alır Func<string>.

Anonim yöntemler (veya lambda sözdizimi) kullanarak iki aşırı yüklemeyi mutlu bir şekilde çağırabilirim, ancak yöntem grubu sözdizimini kullanırsam Ambiguous invocation'ın bir derleyici hatası alıyorum . ActionVeya öğesine açıkça çevrim yaparak geçici bir çözüm bulabilirim Func<string>, ancak bunun gerekli olduğunu düşünmüyorum.

Açık yayınların neden gerekli olduğunu herkes açıklayabilir mi?

Aşağıdaki kod örneği.

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() { }
}

C # 7.3 Güncelleme

0xcde'nin 20 Mart 2019 tarihli aşağıdaki yorumuna göre (bu soruyu gönderdikten dokuz yıl sonra!), Bu kod, gelişmiş aşırı yük adayları sayesinde C # 7.3'ten itibaren derleniyor .


Kodunuzu denedim ve ek bir derleme zamanı hatası alıyorum: 'void test.ClassWithSimpleMethods.DoNothing ()' yanlış dönüş türüne sahip (25. satırda, belirsizlik hatasının olduğu yer)
Matt Ellen

@Matt: O hatayı da görüyorum. Gönderimde bahsettiğim hatalar, siz tam bir derlemeyi denemeden önce VS'nin vurguladığı derleme sorunlarıydı.
Richard Ev

1
Bu arada, bu harika bir soruydu. Beni teknik özelliklere zorlayan her şeyi seviyorum :)
Jon Skeet

1
Örnek kodunuzun C # 7.3 ( <LangVersion>7.3</LangVersion>) veya daha sonrasını kullanırsanız, gelişmiş aşırı yük adayları sayesinde derleneceğini unutmayın .
2019

Yanıtlar:


97

Öncelikle, Jon'un cevabının doğru olduğunu söylememe izin verin. Bu, şartnamenin en tüylü kısımlarından biri, Jon'a ilk önce dalmak için çok iyi.

İkincisi, şunu söyleyeyim:

Bir yöntem grubundan uyumlu bir temsilci türüne örtük bir dönüşüm var

(vurgu eklenmiştir) son derece yanıltıcı ve talihsizdir. Mads ile "uyumlu" kelimesinin buradan kaldırılması hakkında bir konuşma yapacağım.

Bunun yanıltıcı ve talihsiz olmasının nedeni, bunun bölüm 15.2, "Temsilci uyumluluğu" na sesleniyormuş gibi görünmesidir. Bölüm 15.2, yöntemler ve temsilci türleri arasındaki uyumluluk ilişkisini açıkladı , ancak bu , farklı olan yöntem gruplarının ve temsilci türlerinin dönüştürülebilirliği sorunudur .

Artık bunu ortadan kaldırdığımıza göre, şartnamenin 6.6. Bölümünü inceleyebilir ve ne elde ettiğimizi görebiliriz.

Aşırı yük çözümü yapmak için önce hangi aşırı yüklerin uygulanabilir adaylar olduğunu belirlememiz gerekir . Bir aday, tüm argümanlar örtük olarak biçimsel parametre türlerine dönüştürülebilirse uygulanabilir. Programınızın bu basitleştirilmiş sürümünü düşünün:

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);
    }
}

O halde satır satır geçelim.

Bir yöntem grubundan uyumlu bir temsilci türüne örtük bir dönüştürme vardır.

Burada "uyumlu" kelimesinin ne kadar talihsiz olduğunu zaten tartışmıştım. Hareketli. Y (X) üzerinde aşırı yük çözümü yaparken merak ediyoruz, yöntem grubu X D1'e dönüşüyor mu? D2'ye dönüşüyor mu?

Bir temsilci türü D ve bir yöntem grubu olarak sınıflandırılmış bir E ifadesi verildiğinde, E, parametrenin kullanımıyla oluşturulan bir bağımsız değişken listesine uygulanabilir en az bir yöntem [...] içeriyorsa, E'den D'ye örtük bir dönüştürme vardır. D'nin türleri ve değiştiricileri aşağıda açıklandığı gibi.

Çok uzak çok iyi. X, D1 veya D2'nin bağımsız değişken listelerine uygulanabilen bir yöntem içerebilir.

Bir yöntem grubu E'den bir temsilci tipi D'ye bir dönüşümün derleme zamanı uygulaması aşağıda açıklanmaktadır.

Bu satır gerçekten ilginç bir şey söylemiyor.

E'den D'ye örtük bir dönüşümün varlığının, dönüşümün derleme zamanı uygulamasının hatasız bir şekilde başarılı olacağını garanti etmediğini unutmayın.

Bu çizgi büyüleyici. Bu, var olan, ancak hataya dönüştürülebilecek örtük dönüşümler olduğu anlamına gelir! Bu tuhaf bir C # kuralıdır. Konunun dışına çıkmak için işte bir örnek:

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

İfade ağacında artış işlemi geçersizdir. Ancak, lambda hala konvertibl dönüşüm hiç kullanılırsa rağmen ifade ağacı türüne, bunun bir hata olduğunu! Buradaki ilke, daha sonra bir ifade ağacında neyin gidebileceğinin kurallarını değiştirmek isteyebileceğimizdir; bu kuralları değiştirmek , tür sistemi kurallarını değiştirmemelidir . Sizi, programlarınızı şimdi daha açık hale getirmeye zorlamak istiyoruz , böylece gelecekte daha iyi hale getirmek için ifade ağaçlarının kurallarını değiştirdiğimizde, aşırı yük çözümlemesinde kırılma değişiklikleri yapmayız .

Her neyse, bu, bu tür tuhaf kuralların başka bir örneğidir. Aşırı yük çözümü amacıyla bir dönüştürme mevcut olabilir, ancak gerçekten kullanılması bir hata olabilir. Gerçi aslında, burada bulunduğumuz durum tam olarak bu değil.

Hareketli:

E (A) biçimindeki bir yöntem çağrısına karşılık gelen tek bir yöntem M seçilir [...] Argüman listesi A, her biri biçimselde karşılık gelen parametrenin bir değişkeni [...] olarak sınıflandırılan bir ifade listesidir. -D'nin parametre listesi

TAMAM. D1'e göre X üzerinde aşırı yük çözümü yapıyoruz. D1'in biçimsel parametre listesi boş, bu yüzden X () ve joy üzerinde aşırı yük çözümlemesi yapıyoruz, çalışan bir "string X ()" yöntemi buluyoruz. Benzer şekilde, D2'nin resmi parametre listesi boştur. Yine, "string X ()" in burada da çalışan bir yöntem olduğunu görüyoruz.

Buradaki ilke, yöntem grubu dönüştürülebilirliğinin belirlenmesinin aşırı yük çözümlemesini kullanan bir yöntem grubundan bir yöntem seçmeyi gerektirmesidir ve aşırı yük çözümlemesi dönüş türlerini dikkate almaz .

Algoritma [...] bir hata üretirse, derleme zamanı hatası oluşur. Aksi takdirde, algoritma, D ile aynı sayıda parametreye sahip tek bir en iyi yöntem M üretir ve dönüşümün var olduğu kabul edilir.

X yöntem grubunda yalnızca bir yöntem vardır, bu nedenle en iyisi olmalıdır. X'ten D1'e ve X'ten D2'ye bir dönüşümün var olduğunu başarıyla kanıtladık .

Şimdi, bu satır alakalı mı?

Seçilen yöntem M, temsilci tipi D ile uyumlu olmalıdır, aksi takdirde bir derleme zamanı hatası oluşur.

Aslında hayır, bu programda değil. Bu hattı harekete geçirecek kadar ileri gidemeyiz Çünkü unutmayın, burada yaptığımız şey Y (X) üzerinde aşırı yük çözümü yapmaya çalışmaktır. Y (D1) ve Y (D2) olmak üzere iki adayımız var. Her ikisi de uygulanabilir. Hangisi daha iyi ? Spesifikasyonun hiçbir yerinde bu iki olası dönüşüm arasındaki daha iyi tanımlayamıyoruz .

Şimdi, kesinlikle geçerli bir dönüşümün, bir hata üretenden daha iyi olduğu tartışılabilir. Bu, daha sonra, bu durumda, aşırı yük çözümlemesinin dönüş türlerini dikkate aldığını, ki bu da kaçınmak istediğimiz bir şeydir. O zaman soru, hangi ilkenin daha iyi olduğudur: (1) aşırı yük çözümlemesinin dönüş türlerini dikkate almadığı değişmezi koruyun veya (2) çalışmayacağını bildiğimiz bir dönüşüm üzerinde çalışacağını bildiğimiz bir dönüşümü seçmeye çalışın mı?

Bu bir yargılama çağrısıdır. İle lambdas , biz yapmak bölümünde 7.4.3.3 yılında, dönüşümler bu tür dönüş türünü göz önünde bulundurun:

E, anonim bir işlevdir, T1 ve T2, aynı parametre listelerine sahip temsilci türleri veya ifade ağacı türleridir, bu parametre listesi bağlamında E için çıkarılan bir dönüş türü X vardır ve aşağıdaki tutulanlardan biri:

  • T1, Y1 dönüş türüne sahiptir ve T2, Y2 dönüş türüne sahiptir ve X'ten Y1'e dönüşüm, X'ten Y2'ye dönüşümden daha iyidir

  • T1'in dönüş türü Y vardır ve T2 geçersizdir

Metot grubu dönüşümlerinin ve lambda dönüşümlerinin bu açıdan tutarsız olması talihsiz bir durumdur. Ancak onunla yaşayabilirim.

Her neyse, X'den D1'e veya X'den D2'ye hangi dönüşümün daha iyi olduğunu belirleyecek "daha iyi" bir kuralımız yok. Bu nedenle Y (X) çözünürlüğünde bir belirsizlik hatası veriyoruz.


8
Cracking - hem cevap hem de (umarım) spesifikasyondaki sonuçtaki iyileşme için çok teşekkürler :) Kişisel olarak, aşırı yük çözümlemesinin davranışı daha sezgisel hale getirmek için yöntem grubu dönüşümleri için dönüş türünü hesaba katmasının makul olacağını düşünüyorum , ancak Tutarlılık pahasına bunu yapacağını anlıyorum. (Daha önce tartıştığımızı düşündüğüm gibi, yöntem grubunda yalnızca bir yöntem olduğunda yöntem grubu dönüşümlerine uygulanan genel tür çıkarımı için de aynı şey söylenebilir.)
Jon Skeet

35

DÜZENLEME: Sanırım anladım.

Zinglon söylediği gibi bir örtük dönüşüm gerçekleşmesi, bunun nedeni GetStringiçin Actionderleme zamanı uygulama başarısız olur rağmen. Aşağıda, biraz vurgulanarak (benimki) bölüm 6.6'ya giriş yer almaktadır:

Bir yöntem grubundan (§7.1) uyumlu bir temsilci türüne örtük bir dönüştürme (§6.1) mevcuttur. Bir temsilci tipi D ve bir yöntem grubu olarak sınıflandırılan bir E ifadesi verildiğinde, E'nin normal biçiminde (§7.4.3.1) oluşturulan bir bağımsız değişken listesine uygulanabilen en az bir yöntem içermesi durumunda, E'den D'ye örtük bir dönüştürme vardır. aşağıda açıklandığı gibi D'nin parametre türleri ve değiştiricileri kullanılarak .

Şimdi, uyumlu bir temsilci türüne geçişten bahseden ilk cümle kafamı karıştırıyordu. Actionherhangi bir yöntem için bir uyumlu temsilci değildir GetStringyöntem grubunun, ancak GetString()yöntem olup bu, parametre tipleri ve D Not tadil edici maddelerinin kullanımı ile yapılmış bir bağımsız değişken listesine normal biçimde uygulanabilir değildir dönüş türü söz o bocalama neden ... tek bir temsilci uyumluluğunu kontrol çünkü en That D. GetString()zaman uygulayarak dönüşüm, varlığını kontrol etmediğini.

Aşırı yüklemeyi kısaca denklemin dışında bırakmanın ve bir dönüşümün varlığı ile uygulanabilirliği arasındaki bu farkın nasıl tezahür edebileceğini görmenin öğretici olduğunu düşünüyorum . İşte kısa ama eksiksiz bir örnek:

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);
    }
}

Derlemedeki yöntem çağırma ifadelerinin hiçbiri Main, ancak hata mesajları farklıdır. İşte bunlardan biri IntMethod(GetString):

Test.cs (12,9): CS1502 hatası: 'Program.IntMethod (int)' için en iyi aşırı yüklenmiş yöntem eşleşmesinde bazı geçersiz bağımsız değişkenler var

Diğer bir deyişle, spesifikasyonun 7.4.3.1 bölümü herhangi bir uygulanabilir işlev üyesi bulamaz.

Şimdi şunun için hata ActionMethod(GetString):

Test.cs (13,22): CS0407 hatası: 'string Program.GetString ()' yanlış dönüş türüne sahip

Bu sefer çağırmak istediği yöntemi çalıştı - ancak daha sonra gerekli dönüşümü gerçekleştiremedi. Maalesef, son kontrolün yapıldığı spesifikasyon parçasını bulamıyorum - 7.5.5.1'de olabilir gibi görünüyor , ancak tam olarak nerede olduğunu göremiyorum.


Bu kısım dışında eski yanıt kaldırıldı - çünkü Eric'in bu sorunun "neden" ine ışık tutmasını bekliyorum ...

Hâlâ arıyorum ... bu arada, üç kez "Eric Lippert" dersek, bir ziyaret (ve dolayısıyla bir cevap) alacağımızı düşünüyor musunuz?


@Jon - bu olabilir classWithSimpleMethods.GetStringve classWithSimpleMethods.DoNothingdelege değil mi?
Daniel A. White

@Daniel: Hayır - bu ifadeler yöntem grubu ifadeleridir ve aşırı yüklenmiş yöntemler yalnızca yöntem grubundan ilgili parametre türüne örtük bir dönüşüm olduğunda uygulanabilir olarak kabul edilmelidir. Spesifikasyonun 7.4.3.1 bölümüne bakın.
Jon Skeet

Bölüm 6.6 okunduğunda, classWithSimpleMethods.GetString'den Action'a dönüşümün var olduğu düşünülüyor, çünkü parametre listeleri uyumlu, ancak dönüştürme (denenirse) derleme zamanında başarısız oluyor. Bu nedenle, bir örtülü dönüşüm gelmez hem temsilci türleri için var ve çağrı belirsiz.
zinglon

@zinglon: 'dan' ClassWithSimpleMethods.GetStringye bir dönüşümün Actiongeçerli olduğunu belirlemek için §6.6'yı nasıl okuyorsunuz ? Bir yöntem için Mbir temsilci türü ile uyumlu olacak şekilde D(§15.2) "bir kimlik ya da kapalı bir referans dönüşüm dönüş türünden mevcut Mdönüş tipine D."
jason

@ Jason: Spesifikasyon dönüşümün geçerli olduğunu söylemiyor, var olduğunu söylüyor . Aslında, derleme zamanında başarısız olduğu için geçersizdir. §6.6'nın ilk iki noktası, dönüşümün var olup olmadığını belirler. Aşağıdaki noktalar, dönüşümün başarılı olup olmayacağını belirler. 2. noktadan: "Aksi takdirde, algoritma, D ile aynı sayıda parametreye sahip tek bir en iyi yöntem M üretir ve dönüşümün var olduğu kabul edilir." §15.2, 3. noktada çağrılır.
zinglon

1

Kullanılması Func<string>ve Action<string>(Açıkçası çok farklı Actionve Func<string>içinde) ClassWithDelegateMethodsbelirsizlik uzaklaşmaların.

Belirsizlik ayrıca Actionve arasında da ortaya çıkar Func<int>.

Bununla birlikte belirsizlik hatasını da alıyorum:

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; }
} 

Daha ileri deneyler, bir yöntem grubuna kendi başına geçerken, hangi aşırı yüklemenin kullanılacağını belirlerken dönüş türünün tamamen göz ardı edildiğini göstermektedir.

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

Aşırı yükleme Funcve Actionbenzer (çünkü ikisi de delege)

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

void Function() // Action
{
}

Dikkat ederseniz, derleyici hangisini arayacağını bilmez çünkü bunlar yalnızca dönüş türlerine göre farklılık gösterir.


Ben oldukça gerçekte böyle olduğunu sanmıyorum - Bir dönüştürmek olamaz çünkü Func<string>bir içine Action... ve yalnızca bir içine bir dize döndüren bir yöntemden oluşan bir yöntem grubunu dönüştürmek olamaz Actionya.
Jon Skeet

2
Parametresi olmayan ve stringbir Action. Neden belirsizlik olduğunu anlamıyorum.
jason

3
@dtb: Evet, aşırı yüklemeyi kaldırmak sorunu ortadan kaldırır - ancak bu, neden bir sorun olduğunu açıklamaz.
Jon Skeet
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.