Operador de propagación nula y métodos de extensión


He estado mirando Visual Studio 14 CTP junto con C# 6.0 y jugando con el operador de propagación nula.

Sin embargo, no pude encontrar por qué el siguiente código no compila. Las características aún no están documentadas, por lo que no estoy seguro de si se trata de un error o los métodos de extensión simplemente no son compatibles con el operador ?. y el mensaje de error es engañoso.

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

El mensaje de error es:

"ConsoleApplication1.CC" no contiene una definición de "Get" y ningún método de extensión ' Get ' aceptar un primer argumento de tipo 'ConsoleApplication1.CC' se podría encontrar (¿le falta una directiva de uso o una referencia de ensamblaje?)

Author: gunr2171, 2014-06-10

3 answers

No trabajo en el equipo de Roslyn, pero estoy bastante seguro de que esto es un error. Eché un vistazo al código fuente y puedo explicar lo que está pasando.

En primer lugar, no estoy de acuerdo con la respuesta de SLaks de que esto no es compatible porque los métodos de extensión no desreferencian su parámetro this. Es una afirmación infundada, teniendo en cuenta que no hay mención de ella en ninguno de el diseño discusiones . Además, la semántica del operador se convierte en algo eso se parece más o menos al operador ternario ((obj == null) ? null : obj.Member), por lo que no hay realmente una buena razón por la que no pueda ser soportado en un sentido técnico. Quiero decir, cuando se reduce a código generado, realmente no hay diferencia en el this implícito en un método de instancia y el this explícito en el método de extensión estática.

El mensaje de error es una buena pista de que se trata de un error, porque se queja de que el método no existe, cuando en realidad lo hace. Usted puede haber probado esto mediante la eliminación el operador condicional de la llamada, usando el operador de acceso miembro en su lugar, y haciendo que el código se compile correctamente. Si esto fuera un uso ilegal del operador, obtendrías un mensaje similar a este: error CS0023: Operator '.' cannot be applied to operand of type '<type>'.

El error es que cuando el Binder está tratando de vincular la sintaxis a los símbolos compilados, utiliza un método private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [link] que no devuelve el nombre del método que se necesita cuando intenta enlazar la expresión de invocación (nuestra llamada al método).

A una posible solución es agregar una instrucción adicional case al switch en GetNameSyntax[link] como sigue (Archivo: Compilers/CSharp/Source/Binder/Binder_Expressions.cs:2748):

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

Esto probablemente se pasó por alto porque la sintaxis para llamar a métodos de extensión como miembros, es decir, utilizando el operador de acceso a miembros) termina usando un conjunto diferente de sintaxis que cuando se usa con el operador de acceso a miembros vs. el operador de acceso condicional, específicamente, el operador ?. utiliza un MemberBindingExpressionSyntax eso no se tuvo en cuenta para ese método GetNameSyntax.

Curiosamente, el nombre del método no se rellena para el primer var cr = c?.Get(); que compila. Funciona, sin embargo, porque los miembros del grupo del método local se encuentran primero para el tipo y se pasan a la llamada de BindInvocationExpression [link]. Cuando el método se está resolviendo (tenga en cuenta la llamada a ResolveDefaultMethodGroup [link ] antes de intentar BindExtensionMethod [link ]), primero comprueba esos métodos y los encuentra. En el caso de la método de extensión, intenta encontrar un método de extensión que coincida con el nombre del método que se pasó al método, que en este caso era una cadena vacía en lugar de Get, y hace que se muestre el error erróneo.

Con mi versión local de Roslyn con mi corrección de errores, obtengo un ensamblado compilado cuyo código se ve como (regenerado usando 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();
    }
}
 25
Author: Christopher Currens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-10 22:54:33

Sí. Esto es un bicho. Gracias por mencionar esto. Se supone que el ejemplo se compila y debe resultar en una invocación condicional de Get independientemente de si Get es una extensión o no.

Uso de "?."en cc?.Get () es una indicación de que el llamante quiere cc null-checked antes de continuar. Incluso si Get pudiera manejar null de alguna manera, el llamante no quiere que eso suceda.

 29
Author: VSadov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-12 01:56:15

El punto del operador de propagación nula es evitar desrefencionar un valor null.

Sin embargo, los métodos de extensión no deferencia su parámetro this, y de hecho se puede llamar perfectamente bien en null valores.
(aunque es probable que el método en sí mismo lance una excepción si no espera null)

Por lo tanto, no estaría claro si una llamada al método de extensión null-safe omitiría la llamada o no.

 4
Author: SLaks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-10 19:03:22