2017-01-13 12 views
5

Czy jest to błąd kompilatora, czy istnieje konkretny powód, dla którego operator warunkowo-null nie działa z Func wewnątrz ogólnych metod?Operator null-conditional nie działa z Func <T> wewnątrz ogólnej metody

Aby dać przykład następujące nie kompilacji

public static T Test<T>(Func<T> func) 
{ 
    return func?.Invoke() ?? default(T); 
} 

Błąd kompilator produkuje jest CS0023 Operator '?' cannot be applied to operand of type 'T'

Jestem świadomy, że można osiągnąć to samo robi to jednak:

public static T Test<T>(Func<T> func) 
{ 
    return func != null ? func() : default(T); 
} 

Dlaczego więc jest to zabronione?

Aby opracować dalsze Action<T> działa jednak zgodnie z oczekiwaniami.

public static void Test<T>(Action<T> action, T arg) 
{ 
    action?.Invoke(arg); 
} 

Aktualizacja (17.01.2017):

Po kilku dalszych badań, to sprawia, że ​​nawet mniej sensu, nawet z następującą:

Powiedzmy, że mamy klasę (Odnośnik -type)

public class Foo 
{ 
    public int Bar { get; set; } 
} 

i powiedzmy, że mamy Func<int>

Func<int> fun =() => 10; 

następujące utwory:

// This work 
var nullableBar = foo?.Bar; // type of nullableBar is int? 
var bar = nullableBar ?? default(int); // type of bar is int 

// And this work 
nullableBar = fun?.Invoke(); // ditto 
bar = nullableBar ?? default(int); // ditto 

które oznacza według logiki zastosowane tam wtedy Func<T> typu A, o wartości używając null-conditional i null-coalescing operatorów powinno działać.

Jednak, gdy tylko lewy generyczny typ null-conditional jest rodzajowy bez ograniczeń, to nie można zastosować tę samą logikę, że powinien on być w stanie biorąc pod uwagę to może zastosować tę samą logikę do obu typów wartości i typy-odniesienia, gdy typy są jawnie stosowane.

Jestem świadomy ograniczeń kompilatorów, po prostu nie ma dla mnie sensu, dlaczego nie pozwala na to i dlaczego chce, aby wynik był inny, niezależnie od tego, czy jest to referencja czy typ wartości, biorąc pod uwagę ręczne stosowanie typów. przyniesie oczekiwane rezultaty.

+0

'var x = func? .Invoke()' zawiedzie też. 'x' może być puste lub mieć jakąś wartość. kompilator tego nie wie. poza tym kompilator nie wie, czy 'T' jest typem referencyjnym czy nie. zauważ, że 'null' nie jest poprawne dla typów wartości. na przykład nie możesz napisać 'int I = null'. a więc błąd, który otrzymujesz. –

+1

W skrócie, typ 'Func ? .Invoke()' musi być 'T', jeśli' T' jest typem odniesienia, oraz 'T?' Jeśli 'T' jest typem wartości. Ponieważ generics w .NET ma jedną implementację (w przeciwieństwie do szablonów w C++), nie można tego zrobić łatwo. Teoretycznie kompilator może pochylić się do tyłu, aby to zadziałało przez sprytne generowanie kodu. W praktyce filozofią kompilatora C# jest nie pochylanie się do tyłu, ale odrzucanie rzeczy, jeśli nie można tego zrobić bezpośrednio. –

Odpowiedz

6

Niestety Wierzę, że mają trafić przypadek krawędzi kompilatora. Operator ?. musi zwrócić default(RetrunTypeOfRHS) dla klas i default(Nullable<RetrunTypeOfRHS>) dla struktur. Ponieważ nie ograniczyłeś T, aby być klasami lub strukturami, nie możesz określić, do którego promocji ją promować.

Powód: Action<T> działa dlatego, że typ zwrotu po prawej stronie to void dla obu przypadków, więc nie trzeba decydować, którą promocję zrobić.

Trzeba będzie użyć długiego formularz pokazał albo dwa sposoby z różnymi ograniczeniami na T

public static T TestStruct<T>(Func<T> func) where T : struct 
    { 
     return func?.Invoke() ?? default(T); 
    } 

    public static T TestClass<T>(Func<T> func) where T : class 
    { 
     return func?.Invoke(); // ?? default(T); -- This part is unnecessary, ?. already 
               // returns default(T) for classes. 
    } 
+0

'default (T);' kiedy T jest klasą zawsze ma wartość NULL, więc można ją bezpiecznie usunąć. resharper zawsze przypomina mi xD –

+1

@ M.kazemAkhgary odśwież, zbyt wolno :) –

+0

Błąd kompilatora jest dość przerażający. To wcale nie pomaga w ustaleniu, co jest nie tak. +1 – InBetween

6

Należy ustawić ograniczenia na funkcję rodzajowe:

public static T Test<T>(Func<T> func) where T: class 
{ 
    return func?.Invoke() ?? default(T); 
} 

Ponieważ struktura nie może być null i ?. wymaga rodzaj odniesienia.


Ponieważ komentarza od Jeroen Mostert, musiałem spojrzeć na to, co dzieje się pod maską. Numer Func<T> jest delegatem, który jest typem odniesienia. Bez żadnych ograniczeń na kodzie T, kod nie zostanie skompilowany. Error CS0023 Operator '?' cannot be applied to operand of type 'T'. Po dodaniu ograniczenia where T: struct lub where T: class zostanie wygenerowany kod źródłowy.

Kod napisany:

public static T TestStruct<T>(Func<T> func) where T : struct 
    { 
     return func?.Invoke() ?? default(T); 
    } 

    public static T TestClass<T>(Func<T> func) where T : class 
    { 
     return func?.Invoke() ?? default(T); 
    } 

Kod produkowane i dekompilowana z ILSpy:

public static T TestStruct<T>(Func<T> func) where T : struct 
    { 
     return (func != null) ? func.Invoke() : default(T); 
    } 

    public static T TestClass<T>(Func<T> func) where T : class 
    { 
     T arg_27_0; 
     if ((arg_27_0 = ((func != null) ? func.Invoke() : default(T))) == null) 
     { 
      arg_27_0 = default(T); 
     } 
     return arg_27_0; 
    } 

Jak widać, kod wytworzony podczas T jest struktura jest inna niż wtedy, gdy T jest klasa. Naprawiliśmy błąd ?. ALE: Operator ?? nie ma sensu, gdy T jest strukturą. Myślę, że kompilator powinien dać compileerror na ten. Ponieważ używanie ?? na strukturze jest niedozwolone. #BeMoreStrict

Na przykład:

Jeśli piszę:

var g = new MyStruct(); 
var p = g ?? default(MyStruct); 

pojawia się błąd kompilacji:

Error CS0019 Operator '??' cannot be applied to operands of type 'MainPage.MyStruct' and 'MainPage.MyStruct'

+1

'default (T);' ma zawsze wartość null, więc można go bezpiecznie usunąć. –

+1

"'?. "Wymaga typu odniesienia" jest w najlepszym przypadku mylący, a w najgorszym - niepoprawny. 'Func ' * jest * typem referencyjnym, a 'func? .Invoke()' byłoby legalne, gdyby 'Func' było typu' Func '(wynikiem jest typ' int? '). Problemem jest * konkretnie *, że w oryginalnym kodzie zaangażowany jest rodzajowy typ 'T', który może być referencją lub typem wartości. –

+0

@JeroenMostert Dodałem trochę informacji. Dzięki za komentowanie. –