2017-01-13 3 views
5

Является ли это ошибкой компилятора или существует определенная причина, почему оператор с нулевым условием не работает с Func внутри общих методов?Оператор с нулевым условием не работает с Func <T> внутри общего метода

Для примера следующих не компилировать

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

Ошибка компилятор производит это CS0023 Operator '?' cannot be applied to operand of type 'T'

Я знаю, что вы можете достичь того же делает это, однако:

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

Так почему же это запрещено?

Дальнейшая разработка Action<T> работает, как и следовало ожидать.

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

Update (2017-01-17):

После еще некоторых исследований, он делает еще меньше смысла, даже со следующим:

Допустим, у нас есть класс (Reference -типа)

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

и скажем, у нас есть Func<int>

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

следующие работы:

// 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 

что означает, по логике применяется там, то есть Func<T> стоимостного типа с использованием null-conditionalnull-coalescing и операторы должны работать.

Однако, как только левая рука общий тип null-conditional является родовым без каких-либо ограничений, то он не может применить ту же логику, что он должен быть в состоянии с учетом его можно применить ту же логику для обоих ценностных типов и ссылочные типы, когда типы явно применяются.

Я знаю ограничения, связанные с компиляторами, для меня просто не имеет смысла, почему он не позволяет и почему он хочет, чтобы результат был другим, независимо от того, является ли это ссылочным или стоимостным типом, рассматривая вручную применение типов даст ожидаемые результаты.

+0

'вар х = FUNC? .Invoke()' не получится тоже. 'x' может быть нулевым или иметь некоторое значение. компилятор этого не знает. кроме того, что компилятор не знает, является ли тип «T» ссылочным или нет. обратите внимание, что 'null' недопустим для типов значений. например, вы не можете написать 'int I = null'. таким образом, вы получите ошибку. –

+1

В двух словах, тип 'Func? .Invoke()' должен быть 'T', если' T' является ссылочным типом, а 'T?', Если 'T' - тип значения. Поскольку generics в .NET имеют одну реализацию (в отличие от шаблонов на C++), это нелегко сделать. Теоретически, компилятор мог наклониться назад, чтобы сделать эту работу умной генерации кода. На практике философия компилятора C# заключается не в том, чтобы перегибаться назад, а в том, чтобы запрещать вещи, если они не могут быть сделаны прямо. –

ответ

6

К сожалению, я считаю, вы попали крайний случай компилятора. Оператору ?. необходимо вернуть default(RetrunTypeOfRHS) для классов и default(Nullable<RetrunTypeOfRHS>) для структур. Поскольку вы не ограничивались T, чтобы быть классами или структурами, они не могут определить, какой из них следует продвигать.

Причина: Action<T> работает, потому что тип возврата правой стороны - void для обоих случаев, поэтому ему не нужно решать, какую рекламу делать.

Вам нужно будет использовать длинную форму, которую вы проявили или иметь два метода с различными ограничениями на 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);', когда T является классом, всегда null, поэтому его можно безопасно удалить. resharper всегда напоминает мне xD –

+1

@ M.kazemAkhgary обновляется, слишком медленно :) –

+0

Ошибка компилятора довольно ужасная. Это не помогает вообще выяснить, что случилось. +1 – InBetween

6

Вы должны установить ограничение на родовой функции:

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

Поскольку структура не может быть пустой и ?. требует ссылочного типа.


Из комментария от Йерун Мостерта, я имел взгляд на то, что происходит под капотом. A Func<T> является делегатом, который является ссылочным типом. Без каких-либо ограничений на T код не будет компилироваться. Error CS0023 Operator '?' cannot be applied to operand of type 'T'. Когда вы добавите ограничение where T: struct или where T: class, будет создан базовый код.

Код написан:

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

Кодекс производства и декомпилированы с 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; 
    } 

Как вы можете видеть, код, произведенный при T является структура отличается, чем когда T класс. Поэтому мы исправили ошибку ?. НО: Оператор ?? не имеет смысла, когда T является структурой. Я думаю, что компилятор должен дать compilererror на этом. Потому что использование ?? в структуре не допускается. #BeMoreStrict

Например:

Если я пишу:

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

Я получаю ошибку компиляции:

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

+1

'default (T);' всегда равно null, поэтому его можно безопасно удалить. –

+1

«'?? »Требует ссылочного типа« вводит в заблуждение в лучшем случае и неверно в худшем случае. 'Func ' * является * ссылочным типом, а 'func? .Invoke()' будет легальным, если 'Func' был типа' Func '(результат имеет тип' int? '). Проблема * специально *, что в исходном коде задействуется общий тип 'T', который может быть либо ссылочным, либо типом значений. –

+0

@JeroenMostert Я добавил некоторую информацию. Благодарим за комментирование. –

Смежные вопросы