Soru Visual Studio 2013'te C # yöntemi aşırı yükleme sorunları


Bu üç yöntemin Rx.NET kütüphanesinde mevcut olması

public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, CancellationToken, Task> subscribeAsync) {...}
public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, CancellationToken, Task<IDisposable>> subscribeAsync) {...}
public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, CancellationToken, Task<Action>> subscribeAsync) {...}

Aşağıdaki örnek kodu yazdım MSVS 2013:

var sequence =
  Observable.Create<int>( async ( observer, token ) =>
                          {
                            while ( true )
                            {
                              token.ThrowIfCancellationRequested();
                              await Task.Delay( 100, token );
                              observer.OnNext( 0 );
                            }
                          } );

Bu belirsiz aşırı yüklenmeler nedeniyle derlenmiyor. Derleyiciden gelen kesin çıktı:

Error    1    The call is ambiguous between the following methods or properties: 
'System.Reactive.Linq.Observable.Create<int>(System.Func<System.IObserver<int>,System.Threading.CancellationToken,System.Threading.Tasks.Task<System.Action>>)' 
and 
'System.Reactive.Linq.Observable.Create<int>(System.Func<System.IObserver<int>,System.Threading.CancellationToken,System.Threading.Tasks.Task>)'

Ancak ben değiştirir değiştirmez while( true ) ile while( false ) veya ile var condition = true; while( condition )...

var sequence =
  Observable.Create<int>( async ( observer, token ) =>
                          {                            
                            while ( false ) // It's the only difference
                            {
                              token.ThrowIfCancellationRequested();
                              await Task.Delay( 100, token );
                              observer.OnNext( 0 );
                            }
                          } );

hata kaybolur ve yöntem çağrısı buna çözümlenir:

public static IObservable<TResult> Create<TResult>(Func<IObserver<TResult>, CancellationToken, Task> subscribeAsync) {...}

Ne oluyor orada?


28
2018-04-20 09:52


Menşei


Olduğu zaman while(true), derleyici bunun bir yöntemle karşı karşıya bilir asla geri dönmez. Bu, derleyicinin, yöntemin dönüş türünü belirlemeye çalışan bölümünü karıştırdığını düşünüyorum. - Damien_The_Unbeliever
Derleyici çok akıllı. Bir zaman döngüsüne sahip olduğunuz için her zaman yanlış olan derleyici size bir hata vermiyor. Hata mesajı 1, eşit olmayan sayıda açılış ve kapanma açısı braketini göstermektedir. - jdweng
@jdweng: "Her zaman yanlış olan bir süre döngü değil" buradaki özel durum. Amaçları için bu soru, bu durumun, bazen durumun bazen yanlış ve bazen doğru olduğu bir süre döngüsüne eşdeğerdir. Bu, "her zaman doğru olan, herhangi bir break ifadesi olmayan bir süre döngü" dir. Buradaki özel durum, while döngüsünün sonuna ulaşılamaz hale geliyor. - Daisy Shipton
@Mooh gerçek sorun, Visual Studio'nun çok eski bir sürümünü kullanıyor olmanızdır. Visual Studio 2017 ve C # 7 ve hatta C # 5'de repro yok. VS 2015'te Roslyn derleyicisi sabit çok benzer konular. VS 2012 projeleri ile Visual Studio 2015 veya 2017'yi (Topluluk sürümü, özelliklerde Profesyonel'e ücretsiz btw ve eşdeğerdir) kullanabilirsiniz. - Panagiotis Kanavos
@PanagiotisKanavos: Niçin VS2017'de yeniden üretilmediğini araştırıyorum ... C # 6+ aşırı yük çözünürlük kuralları olup olmadığını merak ediyorum. - Daisy Shipton


Cevaplar:


Bu eğlenceli bir tanesi :) Bunun birden çok yönü var. Başlangıç ​​olarak, görüntüden Rx ve gerçek aşırı yük çözünürlüğünü kaldırarak çok önemli ölçüde basitleştirelim. Aşırı yük çözünürlüğü, cevabın sonunda ele alınır.

Dönüşümleri ve erişilebilirliği yetkilendirmek için anonim işlev

Buradaki fark, lambda ifadesinin bitiş noktasının ulaşılabilir olup olmadığıdır. Öyleyse, o lambda ifadesi hiçbir şey döndürmez ve lambda ifadesi sadece bir Func<Task>. Lambda ifadesinin son noktası ise değil ulaşılabilir, o zaman herhangi birine dönüştürülebilir Func<Task<T>>.

Formu while deyimi C # belirtiminin bu kısmı nedeniyle bir fark yaratır. (Bu ECMA C # 5 standardından; diğer versiyonlar aynı konsept için biraz farklı olabilir.)

Bir uç noktası while Aşağıdakilerden en az biri doğruysa ifadeye erişilebilir:

  • while deyimi while ifadesinden çıkan erişilebilir bir break ifadesini içerir.
  • while deyim erişilebilir ve Boole ifadesi sabit değere sahip değil true.

Sahip olduğunuzda while (true) hayırlı döngü break ifadeler, ne mermi doğrudur, yani while ifadesi (ve bu durumda da lambda ifadesi) erişilebilir değil.

İşte herhangi bir Rx içermeyen kısa ama tam bir örnek:

using System;
using System.Threading.Tasks;

public class Test
{
    static void Main()
    {
        // Valid
        Func<Task> t1 = async () => { while(true); };

        // Valid: end of lambda is unreachable, so it's fine to say
        // it'll return an int when it gets to that end point.
        Func<Task<int>> t2 = async () => { while(true); };

        // Valid
        Func<Task> t3 = async () => { while(false); };

        // Invalid
        Func<Task<int>> t4 = async () => { while(false); };
    }
}

Denklemdeki eşitsizliği kaldırarak daha da basitleştirebiliriz. Dönüş ifadesi olmayan senkronize parametreli bir lambda ifademiz varsa, bu her zaman dönüştürülebilir Action, ama o Ayrıca dönüştürülebilir Func<T> herhangi T lambda ifadesinin sonuna ulaşılamıyorsa. Yukarıdaki koda hafif bir değişiklik yapın:

using System;

public class Test
{
    static void Main()
    {
        // Valid
        Action t1 = () => { while(true); };

        // Valid: end of lambda is unreachable, so it's fine to say
        // it'll return an int when it gets to that end point.
        Func<int> t2 = () => { while(true); };

        // Valid
        Action t3 = () => { while(false); };

        // Invalid
        Func<int> t4 = () => { while(false); };
    }
}

Delegeleri ve lambda ifadelerini karışımdan kaldırarak bunu biraz farklı bir şekilde inceleyebiliriz. Bu yöntemleri düşünün:

void Method1()
{
    while (true);
}

// Valid: end point is unreachable
int Method2()
{
    while (true);
}

void Method3()
{
    while (false);
}

// Invalid: end point is reachable
int Method4()
{
    while (false);
}

Her ne kadar hata Method4 "tüm kod yolları bir değer döndürmez" şeklindedir, "algılanan yöntemin sonuna ulaşılır" şeklindedir. Şimdi, bu yöntem gövdelerinin, yöntem imzasıyla aynı imzaya sahip bir temsilci yerine getirmeye çalışan lambda ifadeleri olduğunu ve ikinci örneğe geri döndüğümüzü hayal edin ...

Aşırı yük çözünürlüğü ile eğlenceli

Panagiotis Kanavos'un belirttiği gibi, aşırı yük çözünürlüğünün etrafındaki orijinal hata, Visual Studio 2017'de tekrarlanamaz. Peki, neler oluyor? Yine, bunu test etmek için Rx'e ihtiyaç duymuyoruz. Ama biraz görebiliriz çok garip davranış. Bunu düşün:

using System;
using System.Threading.Tasks;

class Program
{
    static void Foo(Func<Task> func) => Console.WriteLine("Foo1");
    static void Foo(Func<Task<int>> func) => Console.WriteLine("Foo2");

    static void Bar(Action action) => Console.WriteLine("Bar1");
    static void Bar(Func<int> action) => Console.WriteLine("Bar2");

    static void Main(string[] args)
    {
        Foo(async () => { while (true); });
        Bar(() => { while (true) ; });
    }
}

Bu bir uyarı (bekleme operatörleri) verir ancak C # 7 derleyicisi ile derler. Çıktı beni şaşırttı:

Foo1
Bar2

Bu yüzden çözünürlük Foo dönüşümün belirlendiğini Func<Task> dönüşümden daha iyidir Func<Task<int>>, bunun için çözünürlük Bar dönüşümün belirlendiğini Func<int> dönüşümden daha iyidir Action. Tüm dönüşümler geçerlidir. Foo1 ve Bar2 yöntemler, hala derler, ancak çıktı verir Foo2, Bar1.

C # 5 derleyici ile Foo çağrı belirsizdir Bar çağrı çözer Bar2Sadece C # 7 derleyicisiyle olduğu gibi.

Biraz daha fazla araştırma ile senkron form ECMA C # 5 spesifikasyonunun 12.6.4.4'te belirtilmiştir:

C1, aşağıdakilerden en az biri varsa C2'den daha iyi bir dönüşümdür:

  • ...
  • E anonim bir işlevdir, T1 bir deg türü D1 veya bir ifade ağacı türü İfadesidir, T2 bir delege türü D2 veya bir ifade ağacı türü İfade ve aşağıdaki bekletmelerden biridir:      
    • D1, D2'den daha iyi bir dönüşüm hedefi (bizim için alakasız)
    • D1 ve D2 aynı parametre listelerine ve aşağıdakilerden birine sahiptir:
    • D1, bir dönüş türü Y1'e sahiptir ve D2, bir geri dönüş tipi Y2'ye sahiptir, bu parametre listesinin (§12.6.3.13) bağlamında E için çıkarılan bir tür X dönüş değeridir ve X'den Y1'e dönüşüm, dönüşümden daha iyidir. X'den Y2'ye
    • E eş zamansız, D1 bir dönüş tipine sahip Task<Y1>ve D2 bir dönüş türüne sahiptir Task<Y2>, bir tür dönüş türü Task<X> bu parametre listesindeki (§12.6.3.13) E için var ve X'den Y1'e dönüşüm X'den Y2'ye dönüşümden daha iyidir.
    • D1 bir dönüş tipi Y ve D2 geri dönen

Yani bu, asenkron olmayan durum için anlamlıdır - ve C # 5 derleyicisinin belirsizliği nasıl çözemeyeceği konusunda da mantıklıdır, çünkü bu kurallar bağı bozmaz.

Henüz C # 6 veya C # 7 spesifikasyonumuz yok, ancak bir taslak bir müsait. Aşırı yük çözme kuralları biraz farklı olarak ifade ediliyor ve değişim bir yerlerde olabilir.

Eğer bir şey için derleme yapacaksa, Foo aşırı yükleme bir Func<Task<int>> aşırı yükleme kabul edilmek üzere seçilecek Func<Task> - Çünkü daha spesifik bir tip. (Buradan bir referans dönüşümü var Func<Task<int>> için Func<Task>ama tersi değil.)

Unutmayın ki ertelenmiş dönüş tipi lambda ifadesinin sadece Func<Task> Hem C # 5 hem de taslak C # 6 özelliklerinde.

Son olarak, aşırı yük çözünürlüğü ve tür çıkarımı gerçekten zor belirtimin bitleri. Bu cevap nedenini açıklıyor while(true) döngü bir fark yaratır (çünkü onsuz, aşırı yük kabul eden bir func Task<T> Hatta geçerli değil) ama C # 7 derleyicisinin yaptığı seçim hakkında neler yapabileceğimin sonuna ulaştım.


30
2018-04-20 10:04



Visual Studio 2017'de bu sorunu yeniden oluşturamıyorum. Bu, önceden Roslyn derleyicisinin bir yapıtıdır, dilin kendisi değil - Panagiotis Kanavos
@PanagiotisKanavos: Roslyn, her zaman yazılanla ilgili şartlara uymaz, bu nedenle gözlem, hangi derleyicinin "hatalı" olduğunu veya özelliklerin ayarlanması gerekip gerekmediğini belirlemez. (Şu an, derleyicinin ayarlanabileceğinden daha hızlı ilerledikçe, spekilin artık çok uzun sürdüğü gerçeğine değinmiyorum bile). - Jeroen Mostert
"Böylece, Foo'nun çözünürlüğü, Func <Görev <int >> 'e dönüştürmenin Func <Görev>' e dönüşümden daha iyi olduğunu belirlerken, Bar'ın çözünürlüğünün Eylem'e dönüştürmenin Func <dönüşümüne göre daha iyi olduğunu belirlemesidir. int>." Her ne kadar bunun böyle olması gerektiğine inanıyorum (test etmek için elimde hiç bir şey yok), kodunuz / çıktınız tam tersini gösteriyor ... Bir şeyi özledim ya da kafam karıştı. Foo1 öyle mi Func<Task> versiyon ... - René Vogt
@ RenéVogt: Hiçbir şey kaçırmıyorsunuz - Yazarken kafam karıştı. Düzeltecek. - Daisy Shipton


@Daisy Shipton'ın cevabına ek olarak, aşağıdaki durumda da aynı davranışın gözlemlenebileceğini eklemek istiyorum:

var sequence = Observable.Create<int>(
    async (observer, token) =>
    {
        throw new NotImplementedException();
    });

temelde aynı nedenden dolayı - derleyici lambda fonksiyonunun hiçbir zaman geri dönmeyeceğini ve böylece herhangi bir dönüş türünün eşleşeceğini görür, Observable.Create aşırı yükler.

Ve son olarak, basit bir çözüm örneği: Rb aşırı yüklenmeyi seçen derleyiciyi ima etmek için lambdayı istenilen imza türüne çevirebilirsiniz.

var sequence =
    Observable.Create<int>(
        (Func<IObserver<int>, CancellationToken, Task>)(async (observer, token) =>
        {
            throw new NotImplementedException();
        })
      );

5
2018-04-20 10:34