¿Por qué la compilación está bien cuando uso el método Invoke y no está bien cuando devuelvo Func <int, int> directamente?

28

No entiendo este caso:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

¿Por qué la compilación está bien cuando uso el Invokemétodo y no está bien cuando regreso csharp Func<int,int>directamente?

Evgeniy Terekhin
fuente
Tienes un delegado, lo que significa que estás recibiendo algún tipo de evento. La invocación evita la excepción de subprocesos cruzados y permite que múltiples procesos accedan al objeto.
jdweng
Tenga en cuenta que verá este problema incluso si utiliza dos delegados de apariencia idéntica como delegate void test1(int i);ydelegate void test2(int i);
Matthew Watson

Respuestas:

27

Hay dos cosas que necesita saber para comprender este comportamiento.

  1. Todos los delegados se derivan System.Delegate, pero diferentes delegados tienen diferentes tipos y, por lo tanto, no pueden asignarse entre sí.
  2. El lenguaje C # proporciona un manejo especial para asignar un método o lambda a un delegado .

Debido a que diferentes delegados tienen diferentes tipos, eso significa que no puede asignar un delegado de un tipo a otro.

Por ejemplo, dado:

delegate void test1(int i);
delegate void test2(int i);

Entonces:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

La primera línea anterior compila OK porque está usando el manejo especial para asignar una lambda o un método a un delegado.

De hecho, esta línea es efectivamente reescrita así por el compilador:

test1 a = new test1(Console.WriteLine);

La segunda línea anterior no se compila porque está intentando asignar una instancia de un tipo a otro tipo incompatible.

En cuanto a los tipos, no hay una asignación compatible entre test1y test2porque son tipos diferentes.

Si ayuda pensarlo, considere esta jerarquía de clases:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

El siguiente código no se compilará, a pesar de que Test1y Test2derivar de la misma clase base:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

Esto explica por qué no puede asignar un tipo de delegado a otro. Ese es solo el lenguaje normal de C #.

Sin embargo, lo crucial es entender por qué se le permite asignar un método o lambda a un delegado compatible. Como se señaló anteriormente, esto es parte del soporte de lenguaje C # para los delegados.

Así que finalmente para responder tu pregunta:

Cuando lo usa Invoke(), está asignando una llamada de MÉTODO al delegado usando el manejo especial del lenguaje C # para asignar métodos o lambdas a un delegado en lugar de intentar asignar un tipo incompatible, por lo tanto, se compila OK.

Para ser completamente claro, el código que compila en su OP:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

En realidad se convierte conceptualmente en algo como:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

Mientras que el código que falla intenta asignar entre dos tipos incompatibles:

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}
Matthew Watson
fuente
6

En el segundo caso, fes de tipo Func<int, int>, pero se dice que el método devuelve a test. Estos son tipos no relacionados (delegados), que no son convertibles entre sí, por lo que se produce un error del compilador. Puede ir a esta sección de las especificaciones de idioma y buscar "delegar". No encontrará ninguna mención de conversiones entre delegados que tengan las mismas firmas.

Sin embargo, en el primer caso, f.Invokees una expresión de grupo de método , que en realidad no tiene un tipo. El compilador de C # convertirá expresiones de grupos de métodos a tipos de delegados específicos según el contexto, a través de una conversión de grupo de métodos .

(Citando la quinta viñeta aquí , énfasis mío)

Una expresión se clasifica como una de las siguientes:

  • ...

  • Un grupo de métodos, que es un conjunto de métodos sobrecargados que resultan de una búsqueda de miembros. [...] Se permite un grupo de métodos en una invocación_expresión, una delegación_creación_expresión y como el lado izquierdo de un isoperador, y se puede convertir implícitamente a un tipo de delegado compatible.

En este caso, se convierte al testtipo de delegado.

En otras palabras, return fno funciona porque fya tiene un tipo, pero f.Invokeaún no lo tiene.

Barrendero
fuente
2

El problema aquí es la compatibilidad de tipos:

La siguiente es la definición de delegado de Func de las fuentes de MSDN:

public delegate TResult Func<in T, out TResult>(T arg);

Si ve que no hay una relación directa entre el Func mencionado anteriormente y su Delegado definido:

public delegate int test(int i);

¿Por qué compila el primer fragmento?

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

Los delegados se comparan usando la firma, que es parámetros de entrada y resultado de salida, en última instancia, un delegado es un puntero de función y dos funciones se pueden comparar solo a través de la firma. En tiempo de ejecución, el método invocado a través de Func se asigna al Testdelegado, ya que Signature es el mismo y funciona a la perfección. Es una asignación de puntero de función, dondeTest delegado ahora invocará el método señalado por el delegado de Func

¿Por qué el segundo fragmento no puede compilarse?

Entre Func y el delegado de prueba, no hay compatibilidad de tipo / asignación, Func no puede completar como parte de las reglas del sistema Tipo. Incluso cuando su resultado se puede asignar y completar test delegatecomo se hizo en el primer caso.

Mrinal Kamboj
fuente