35 votes

Opérateur de propagation nul et méthodes d'extension

J'ai été à la recherche à Visual Studio 14 CTP avec C# 6.0 et jouer avec le null-propagation de l'opérateur.

Cependant, je ne pouvais pas trouver pourquoi le code suivant ne compile pas. Les fonctionnalités ne sont pas encore documentées donc je ne suis pas sûr de savoir si c'est un bug ou d'une extension des méthodes ne sont tout simplement pas pris en charge avec l' ?. de l'opérateur et le message d'erreur est trompeur.

class C
{
    public object Get()
    {
        return null;
    }
}

class CC
{
}

static class CCExtensions
{
    public static object Get(this CC c)
    {
        return null;
    }
}

class Program
{
    static void Main(string[] args)
    {
        C c = null;
        var cr = c?.Get();   //this compiles (Get is instance method)

        CC cc = null;
        var ccr = cc?.Get(); //this doesn't compile

        Console.ReadLine();
    }
}

Message d'erreur est:

'ConsoleApplication1.CC' ne contient pas une définition pour 'Get' et aucune méthode d'extension 'Get' acceptant un premier argument de type 'ConsoleApplication1.CC' a pu être trouvé (vous manque une directive using ou une référence d'assembly?)

30voto

VSadov Points 191

Oui. C'est un bug. Merci d'avoir soulevé cette question. L'exemple est censé compiler et doit aboutir à un appel conditionnel de Get, que Get soit une extension ou non.

L'utilisation de "?." in cc? .Get () indique que l'appelant veut que cc soit vérifié par zéro avant de continuer. Même si Get peut gérer null d'une manière ou d'une autre, l'appelant ne veut pas que cela se produise.

27voto

Je ne travaille pas sur le Roslyn équipe, mais je suis assez confiant que c'est un bug. J'ai pris un coup d'oeil au code source et je peux expliquer ce qui se passe.

Tout d'abord, je suis en désaccord avec SLaks répondre que ce n'est pas pris en charge car les méthodes d'extension ne sont pas de déréférencement de leur this paramètre. C'est une réclamation non fondée, considérant qu'il n'y a aucune mention de cela dans toute la conception de discussions. De Plus, la sémantique de l'opérateur de tourner en somethat qui ressemble plus ou moins à l'opérateur ternaire ((obj == null) ? null : obj.Member), donc il n'y a pas vraiment une bonne raison pourquoi elle ne pouvait pas être pris en charge dans un sens technique. Je veux dire, lorsqu'il s'agit de code généré, il n'y a vraiment pas de différence dans l'implicite this sur une méthode d'instance et de l'explicite this sur la statique de la méthode d'extension.

Le message d'erreur est une bonne idée de ce que c'est un bug, parce que c'est de se plaindre de ce que la méthode n'existe pas, quand il fait réellement. Vous pouvez avoir testé en enlevant l'opérateur conditionnel à partir de l'appel, à l'aide de l'opérateur d'accès au membre au lieu de cela, et d'avoir le code de compiler avec succès. Si il s'agissait d'une utilisation illégale de l'opérateur, vous recevez un message de ce type: error CS0023: Operator '.' cannot be applied to operand of type '<type>'.

Le bug, c'est que lorsque l' Binder tente de lier la syntaxe de la compilation des symboles, il utilise une méthode de private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [lien] qui est le défaut de retourner le nom de la méthode qui est nécessaire quand il essaie de lier l'invocation de l'expression (notre appel de méthode).

Une solution possible consiste à ajouter un extra - case déclaration à l'interrupteur en GetNameSyntax[lien] comme suit (Fichier: Compilateurs/CSharp/Source/Liant/Binder_Expressions.cs:2748):

// ...
case SyntaxKind.MemberBindingExpression:
     return ((MemberBindingExpressionSyntax)syntax).Name;
// ...

C'était sans doute négligé parce que la syntaxe pour appeler les méthodes d'extension en tant que membres, c'est à l'aide de l'opérateur d'accès au membre du vent à l'aide d'un ensemble différent de syntaxe que lorsqu'il est utilisé avec l'opérateur d'accès au membre contre la condition de l'accès de l'opérateur, en particulier, l' ?. opérateur utilise un MemberBindingExpressionSyntax qui n'était pas pris en compte pour qu' GetNameSyntax méthode.

Fait intéressant, le nom de la méthode n'est pas renseigné pour la première var cr = c?.Get(); qui compile. Il fonctionne, cependant, parce que la méthode local les membres du groupe sont d'abord trouvé pour le type et sont passés à l'appel de l' BindInvocationExpression [lien]. Lorsque la méthode est résolu (note de l'appel à ResolveDefaultMethodGroup [lien] avant d'essayer de l' BindExtensionMethod [lien]), il vérifie d'abord les méthodes et la trouve. Dans le cas de la méthode d'extension, il essaie de trouver une méthode d'extension qui correspond le nom de la méthode qui a été adoptée dans la méthode, qui dans ce cas est une chaîne vide à la place de Get, et les causes de l'erreur d'erreur sera affiché.

Avec ma version locale de Roslyn avec mon bug fix, je reçois un assembly compilé dont le code ressemble (régénérée à l'aide de dotPeek):

internal class Program
{
    private static void Main(string[] args)
    {
        C c1 = (C) null;
        object obj1 = c1 != null ? c1.Get() : (object) null;
        CC c2 = (CC) null;
        object obj2 = c2 != null ? CCExtensions.Get(c2) : (object) null;
        Console.ReadLine();
    }
}

4voto

SLaks Points 391154

Le point de l'opérateur de propagation nulle est d'éviter de déréférencer une valeur null .

Cependant, les méthodes d'extension ne respectent pas leur paramètre this et peuvent en fait être appelées parfaitement bien sur les valeurs null .
(bien que la méthode elle-même soit susceptible de lever une exception si elle ne s'attend pas à null )

Par conséquent, il ne serait pas clair si un appel de méthode d'extension null-safe ignorerait l'appel ou non.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X