2017-01-13 2 views
5

Est-ce un bogue de compilateur ou existe-t-il une raison spécifique pour laquelle l'opérateur null-conditionnel ne fonctionne pas avec Func dans les méthodes génériques?L'opérateur null-conditionnel ne fonctionne pas avec Func <T> dans une méthode générique

Pour donner un exemple ce qui suit ne compile pas

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

L'erreur du compilateur produit est CS0023 Operator '?' cannot be applied to operand of type 'T'

Je suis conscient que vous pouvez obtenir le même faisant cela cependant:

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

Alors, pourquoi est-ce interdit? Pour compléter davantage, Action<T> fonctionne cependant comme prévu.

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

Mise à jour (17/01/2017):

Après quelques recherches, il est encore moins de sens, même avec les éléments suivants:

Disons que nous avons une classe (Référence -type)

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

et disons que nous avons un Func<int>

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

Les travaux suivants:

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

Ce qui signifie selon la logique, il applique alors un Func<T> d'une valeur de type en utilisant null-conditional et null-coalescing opérateurs devraient travailler.

Cependant, dès que le type générique gauche du null-conditional est générique sans contraintes, il ne peut pas appliquer la même logique qu'il devrait pouvoir considérer, il peut appliquer la même logique aux deux types de valeurs et types-références lorsque les types sont explicitement appliqués. Je connais les contraintes des compilateurs, cela n'a pas de sens pour moi pourquoi il ne le permet pas et pourquoi il veut que la sortie soit différente que ce soit une référence ou un type de valeur considérant l'application manuelle des types donnera les résultats attendus.

+0

'var x = func? .invoke()' échouera aussi. 'x' peut être nul ou avoir une certaine valeur. le compilateur ne le sait pas. d'ailleurs ce compilateur ne sait pas si 'T' est le type de référence ou non. notez que 'null' n'est pas valide sur les types de valeur. par exemple vous ne pouvez pas écrire 'int I = null'. donc l'erreur que vous obtenez. –

+1

En un mot, le type 'Func ? .Invoke()' doit être 'T' si' T' est un type de référence, et 'T?' Si 'T' est un type de valeur. Puisque les génériques dans .NET ont une implémentation (contrairement aux templates en C++), cela ne peut pas être fait facilement. En théorie, le compilateur pourrait se pencher en arrière pour que cela fonctionne grâce à une génération intelligente de code. En pratique, la philosophie du compilateur C# n'est pas de se pencher en arrière mais de refuser des choses si elles ne peuvent pas être faites directement. –

Répondre

6

Malheureusement, je crois que vous avez touché une affaire de bord du compilateur. L'opérateur ?. doit renvoyer default(RetrunTypeOfRHS) pour les classes et default(Nullable<RetrunTypeOfRHS>) pour les structures. Parce que vous n'avez pas contraint T à être des classes ou des structs, il ne peut pas dire lequel promouvoir.

La raison Action<T> fonctionne parce que le type de retour du côté droit est void pour les deux cas, il n'a donc pas besoin de décider quelle promotion faire.

Vous aurez besoin d'utiliser la forme longue ou que vous avez montré deux méthodes avec des contraintes différentes sur 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

'défaut (T);' quand T est la classe est toujours nulle, donc il peut être retiré en toute sécurité. resharper me rappelle toujours xD –

+1

@ M.kazemAkhgary rafraîchir, trop lent :) –

+0

L'erreur du compilateur est assez terrible. Cela n'aide pas du tout à comprendre ce qui ne va pas. +1 – InBetween

6

Vous devez définir une contrainte sur la fonction générique:

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

Parce qu'un struct ne peut pas être nulle et le ?. exige un type de référence.


En raison du commentaire de Jeroen Mostert, j'ai eu un coup d'œil sur ce qui se passe sous le capot. Un Func<T> est un délégué qui est un type de référence. Sans aucune contrainte sur le T, le code ne sera pas compilé. Error CS0023 Operator '?' cannot be applied to operand of type 'T'. Lorsque vous ajoutez la contrainte where T: struct ou where T: class, le code sous-jacent sera produit.

code écrit:

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

code produit et décompilé avec 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; 
    } 

Comme vous pouvez le voir, le code produit lorsque T est un struct est différent de celui où T est une classe. Nous avons donc corrigé l'erreur ?. MAIS: L'opérateur ?? n'a pas de sens lorsque T est une structure. Je pense que le compilateur devrait donner une compilation sur ce. Car l'utilisation de ?? sur une structure n'est pas autorisée. #BeMoreStrict

Par exemple:

Si j'écris:

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

Je reçois l'erreur de compilation:

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

+1

'default (T);' est toujours nul, donc peut être supprimé en toute sécurité. –

+1

"'? .' nécessite un type de référence "est trompeur au mieux et incorrect au pire. 'Func ' * est * un type de référence, et 'func? .Invoke()' serait légal si 'Func' était de type' Func '(le résultat est de type' int? '). Le problème est * spécifiquement * que dans le code original, un type générique 'T' est impliqué qui pourrait être soit une référence ou un type de valeur. –

+0

@JeroenMostert J'ai ajouté quelques informations. Merci d'avoir commenté. –