with when microsoft funciona event delegate como and c# delegates

when - func c#



Error de invocación ambigua del compilador: método anónimo y grupo de métodos con Func<> o Action (4)

Tengo un escenario en el que deseo utilizar la sintaxis del grupo de métodos en lugar de los métodos anónimos (o la sintaxis lambda) para llamar a una función.

La función tiene dos sobrecargas, una que toma una Action , la otra toma una Func<string> .

Felizmente puedo llamar a las dos sobrecargas usando métodos anónimos (o sintaxis lambda), pero obtengo un error de compilación de invocación ambigua si utilizo la sintaxis del grupo de métodos. Puedo solucionarlo mediante conversión explícita a Action o Func<string> , pero no creo que esto sea necesario.

¿Alguien puede explicar por qué los moldes explícitos deberían ser requeridos?

Muestra de código a continuación.

class Program { static void Main(string[] args) { ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods(); ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods(); // These both compile (lambda syntax) classWithDelegateMethods.Method(() => classWithSimpleMethods.GetString()); classWithDelegateMethods.Method(() => classWithSimpleMethods.DoNothing()); // These also compile (method group with explicit cast) classWithDelegateMethods.Method((Func<string>)classWithSimpleMethods.GetString); classWithDelegateMethods.Method((Action)classWithSimpleMethods.DoNothing); // These both error with "Ambiguous invocation" (method group) classWithDelegateMethods.Method(classWithSimpleMethods.GetString); classWithDelegateMethods.Method(classWithSimpleMethods.DoNothing); } } class ClassWithDelegateMethods { public void Method(Func<string> func) { /* do something */ } public void Method(Action action) { /* do something */ } } class ClassWithSimpleMethods { public string GetString() { return ""; } public void DoNothing() { } }


EDITAR: Creo que lo tengo.

Como dice zinglon, es porque hay una conversión implícita de GetString a Action aunque la aplicación en tiempo de compilación fallaría. Aquí está la introducción a la sección 6.6, con cierto énfasis (mío):

Existe una conversión implícita (§6.1) de un grupo de métodos (§7.1) a un tipo de delegado compatible. Dado un delegado tipo D y una expresión E que se clasifica como un grupo de métodos, existe una conversión implícita de E a D si E contiene al menos un método que es aplicable en su forma normal (§7.4.3.1) a una lista de argumentos construida mediante el uso de los tipos de parámetros y modificadores de D , como se describe a continuación.

Ahora, estaba confundido por la primera oración, que habla de una conversión a un tipo de delegado compatible. Action no es un delegado compatible para ningún método en el grupo de métodos GetString , pero el método GetString() es aplicable en su forma normal a una lista de argumentos construida mediante el uso de los tipos de parámetros y modificadores de D. Tenga en cuenta que esto no habla sobre el tipo de devolución de D. Es por eso que se está confundiendo ... porque solo verificaría la compatibilidad del delegado de GetString() al aplicar la conversión, sin verificar su existencia.

Creo que es instructivo dejar brevemente la sobrecarga de la ecuación y ver cómo se puede manifestar esta diferencia entre la existencia de una conversión y su aplicabilidad . Aquí hay un ejemplo corto pero completo:

using System; class Program { static void ActionMethod(Action action) {} static void IntMethod(int x) {} static string GetString() { return ""; } static void Main(string[] args) { IntMethod(GetString); ActionMethod(GetString); } }

Ninguna de las expresiones de invocación de método en compilaciones Main , pero los mensajes de error son diferentes. Aquí está el de IntMethod(GetString) :

Test.cs (12,9): error CS1502: la mejor coincidencia de método sobrecargado para ''Program.IntMethod (int)'' tiene algunos argumentos inválidos

En otras palabras, la sección 7.4.3.1 de la especificación no puede encontrar ningún miembro de función aplicable.

Ahora aquí está el error para ActionMethod(GetString) :

Test.cs (13,22): error CS0407: ''cadena Program.GetString ()'' tiene el tipo de retorno incorrecto

Esta vez se resolvió el método al que desea llamar, pero falló al realizar la conversión requerida. Lamentablemente, no puedo encontrar el fragmento de la especificación donde se realiza esa comprobación final, parece que podría estar en 7.5.5.1, pero no puedo ver exactamente dónde.

Se eliminó la respuesta anterior, excepto por este bit, porque supongo que Eric podría arrojar luz sobre el "por qué" de esta pregunta ...

Todavía mirando ... mientras tanto, si decimos "Eric Lippert" tres veces, ¿cree que tendremos una visita (y por lo tanto una respuesta)?


El uso de Func<string> y Action<string> (obviamente muy diferente de Action y Func<string> ) en ClassWithDelegateMethods elimina la ambigüedad.

La ambigüedad también se produce entre Action y Func<int> .

También recibo el error de ambigüedad con esto:

class Program { static void Main(string[] args) { ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods(); ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods(); classWithDelegateMethods.Method(classWithSimpleMethods.GetOne); } } class ClassWithDelegateMethods { public void Method(Func<int> func) { /* do something */ } public void Method(Func<string> func) { /* do something */ } } class ClassWithSimpleMethods { public string GetString() { return ""; } public int GetOne() { return 1; } }

La experimentación adicional muestra que al pasar en un grupo de métodos por su cuenta, el tipo de devolución se ignora por completo al determinar qué sobrecarga usar.

class Program { static void Main(string[] args) { ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods(); ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods(); //The call is ambiguous between the following methods or properties: //''test.ClassWithDelegateMethods.Method(System.Func<int,int>)'' //and ''test.ClassWithDelegateMethods.Method(test.ClassWithDelegateMethods.aDelegate)'' classWithDelegateMethods.Method(classWithSimpleMethods.GetX); } } class ClassWithDelegateMethods { public delegate string aDelegate(int x); public void Method(Func<int> func) { /* do something */ } public void Method(Func<string> func) { /* do something */ } public void Method(Func<int, int> func) { /* do something */ } public void Method(Func<string, string> func) { /* do something */ } public void Method(aDelegate ad) { } } class ClassWithSimpleMethods { public string GetString() { return ""; } public int GetOne() { return 1; } public string GetX(int x) { return x.ToString(); } }


En primer lugar, permítanme decir que la respuesta de Jon es correcta. Esta es una de las partes más peludas de la especificación, tan buena en Jon para sumergirse en ella de cabeza.

En segundo lugar, déjame decir que esta línea:

Existe una conversión implícita de un grupo de métodos a un tipo de delegado compatible

(énfasis agregado) es profundamente engañoso y desafortunado. Tendré una charla con Mads sobre cómo eliminar la palabra "compatible" aquí.

La razón por la que esto es engañoso y desafortunado es porque parece que está llamando a la sección 15.2, "Delegar compatibilidad". La Sección 15.2 describe la relación de compatibilidad entre métodos y tipos de delegado , pero esta es una cuestión de convertibilidad de grupos de métodos y tipos de delegados , que es diferente.

Ahora que lo hemos quitado del camino, podemos recorrer la sección 6.6 de la especificación y ver qué obtenemos.

Para hacer una resolución de sobrecarga, primero debemos determinar qué sobrecargas son candidatos aplicables . Un candidato es aplicable si todos los argumentos son implícitamente convertibles a los tipos de parámetros formales. Considere esta versión simplificada de su programa:

class Program { delegate void D1(); delegate string D2(); static string X() { return null; } static void Y(D1 d1) {} static void Y(D2 d2) {} static void Main() { Y(X); } }

Entonces, repasemos línea por línea.

Existe una conversión implícita de un grupo de métodos a un tipo de delegado compatible.

Ya he discutido cómo la palabra "compatible" es desafortunada aquí. Continuando. Nos preguntamos si al hacer una resolución de sobrecarga en Y (X), ¿el grupo de métodos X se convierte en D1? ¿Se convierte a D2?

Dado un delegado tipo D y una expresión E que se clasifica como un grupo de métodos, existe una conversión implícita de E a D si E contiene al menos un método que es aplicable a una lista de argumentos construida mediante el uso del parámetro tipos y modificadores de D, como se describe a continuación.

Hasta aquí todo bien. X podría contener un método que sea aplicable con las listas de argumentos de D1 o D2.

La aplicación en tiempo de compilación de una conversión de un grupo de métodos E a un tipo de delegado D se describe a continuación.

Esta línea realmente no dice nada interesante.

Tenga en cuenta que la existencia de una conversión implícita de E a D no garantiza que la aplicación en tiempo de compilación de la conversión tenga éxito sin error.

Esta línea es fascinante. Significa que existen conversiones implícitas que existen, pero que están sujetas a convertirse en errores. Esta es una regla extraña de C #. Para hacer una digresión por un momento, he aquí un ejemplo:

void Q(Expression<Func<string>> f){} string M(int x) { ... } ... int y = 123; Q(()=>M(y++));

Una operación de incremento es ilegal en un árbol de expresiones. Sin embargo, el lambda todavía es convertible al tipo de árbol de expresión, aunque si la conversión se usa alguna vez, ¡es un error! El principio aquí es que podríamos querer cambiar las reglas de lo que puede ir en un árbol de expresiones más tarde; cambiar esas reglas no debería cambiar las reglas del sistema tipo . Queremos obligarlo a que sus programas sean ahora inequívocos, de modo que cuando cambiemos las reglas para árboles de expresiones en el futuro para mejorarlos, no introduzcamos cambios bruscos en la resolución de sobrecarga .

De todos modos, este es otro ejemplo de este tipo de regla extraña. Una conversión puede existir para propósitos de resolución de sobrecarga, pero puede ser un error de usar. Aunque de hecho, esa no es exactamente la situación en la que estamos aquí.

Continuando:

Se selecciona un solo método M correspondiente a una invocación de método de la forma E (A) [...] La lista de argumentos A es una lista de expresiones, cada una de ellas clasificada como una [...] variable del parámetro correspondiente en la forma formal -parameter-list of D.

DE ACUERDO. Entonces sobrecargamos la resolución en X con respecto a D1. La lista de parámetros formales de D1 está vacía, por lo tanto, sobrecargamos la resolución en X () y joy, encontramos un método "string X ()" que funciona. De manera similar, la lista de parámetros formales de D2 está vacía. Nuevamente, encontramos que "string X ()" es un método que también funciona aquí.

El principio aquí es que determinar la convertibilidad del grupo de métodos requiere seleccionar un método de un grupo de métodos que usa resolución de sobrecarga , y la resolución de sobrecarga no considera los tipos de devolución .

Si el algoritmo [...] produce un error, se produce un error en tiempo de compilación. De lo contrario, el algoritmo produce un único mejor método M que tiene el mismo número de parámetros que D y se considera que existe la conversión.

Solo hay un método en el grupo de métodos X, por lo que debe ser el mejor. Hemos demostrado con éxito que existe una conversión de X a D1 y de X a D2.

Ahora, ¿esta línea es relevante?

El método M seleccionado debe ser compatible con el tipo de delegado D, o de lo contrario, se produce un error en tiempo de compilación.

En realidad, no, no en este programa. Nunca llegamos tan lejos como para activar esta línea. Porque, recuerde, lo que estamos haciendo aquí es intentar hacer una resolución de sobrecarga en Y (X). Tenemos dos candidatos Y (D1) e Y (D2). Ambos son aplicables. ¿Cuál es mejor ? En ninguna parte de la especificación describimos la mejoría entre estas dos conversiones posibles .

Ahora bien, uno podría argumentar que una conversión válida es mejor que una que produce un error. Eso estaría diciendo efectivamente, en este caso, que la resolución de sobrecarga SÍ considera los tipos de devolución, que es algo que queremos evitar. La pregunta es, entonces, qué principio es mejor: (1) mantener el invariante que la resolución de sobrecarga no considera los tipos de devolución, o (2) intentar elegir una conversión que sabemos que funcionará sobre una que sabemos que no lo hará?

Esta es una llamada de juicio. Con lambdas , consideramos el tipo de devolución en este tipo de conversiones, en la sección 7.4.3.3:

E es una función anónima, T1 y T2 son tipos de delegados o tipos de árbol de expresiones con listas de parámetros idénticas, existe un retorno inferido de tipo X para E en el contexto de esa lista de parámetros, y una de las siguientes retenciones:

  • T1 tiene un tipo de retorno Y1, y T2 tiene un tipo de retorno Y2, y la conversión de X a Y1 es mejor que la conversión de X a Y2

  • T1 tiene un tipo de retorno Y, y T2 no tiene retorno

Es lamentable que las conversiones de grupo de métodos y las conversiones de lambda sean inconsistentes a este respecto. Sin embargo, puedo vivir con eso.

De todos modos, no tenemos una regla de "mejora" para determinar qué conversión es mejor, de X a D1 o de X a D2. Por lo tanto, damos un error de ambigüedad en la resolución de Y (X).


La sobrecarga con Func y Action es similar (porque ambos son delegados) a

string Function() // Func<string> { } void Function() // Action { }

Si observa, el compilador no sabe a cuál llamar porque solo difieren en los tipos de devolución.