tipo sintaxis puede funciones expresiones expresion español delegado convertir c# lambda named-parameters

sintaxis - Pasar funciones lambda como parámetros nombrados en C#



lambda sintaxis (3)

¿Por qué no informa el error real?

No, ese es el problema; está informando el error real.

Déjame explicarte con un ejemplo un poco más complicado. Supongamos que tienes esto:

class CustomerCollection { public IEnumerable<R> Select<R>(Func<Customer, R> projection) {...} } .... customers.Select( (Customer c)=>c.FristNmae );

OK, ¿cuál es el error de acuerdo con la especificación C # ? Tienes que leer la especificación con mucho cuidado aquí. Vamos a resolverlo.

  • Tenemos una llamada a Seleccionar como una llamada a función con un solo argumento y sin argumentos de tipo. Realizamos una búsqueda en Select in CustomerCollection, buscando elementos invocables llamados Select, es decir, campos como los de tipo de delegado o métodos. Como no tenemos especificados los argumentos de tipo, coincidimos en cualquier método genérico Select. Encontramos uno y creamos un grupo de métodos a partir de él. El grupo de métodos contiene un solo elemento.

  • El grupo de métodos ahora debe analizarse mediante resolución de sobrecarga para determinar primero el conjunto de candidatos y, a continuación, determinar el conjunto de candidatos aplicable y, a partir de ese momento, determinar cuál es el mejor candidato aplicable y determinar el mejor candidato aplicable finalmente validado . Si cualquiera de esas operaciones falla, entonces la resolución de sobrecarga debe fallar con un error. ¿Cuál de ellos falla?

  • Comenzamos construyendo el conjunto de candidatos. Para obtener un candidato, debemos realizar una inferencia de tipo de método para determinar el valor del tipo de argumento R. ¿Cómo funciona la inferencia de tipo de método?

  • Tenemos una lambda cuyos tipos de parámetros son conocidos: el parámetro formal es Cliente. Para determinar R, debemos hacer un mapeo desde el tipo de retorno de la lambda a R. ¿Cuál es el tipo de retorno de la lambda?

  • Suponemos que c es Cliente e intentamos analizar el cuerpo lambda. Al hacerlo, se realiza una búsqueda de FristNmae en el contexto de Cliente, y la búsqueda falla.

  • Por lo tanto, la inferencia de tipo de retorno lambda falla y no se agrega límite a R.

  • Después de analizar todos los argumentos, no hay límites en R. Por lo tanto, la inferencia de tipo de método no puede determinar un tipo para R.

  • Por lo tanto, la inferencia de tipo de método falla.

  • Por lo tanto, no se agrega ningún método al conjunto de candidatos.

  • Por lo tanto, el conjunto de candidatos está vacío.

  • Por lo tanto, no puede haber candidatos aplicables.

  • Por lo tanto, el mensaje de error correcto aquí sería algo así como "la resolución de sobrecarga no pudo encontrar un candidato mejor aplicable finalmente validado porque el conjunto candidato estaba vacío".

Los clientes estarían muy descontentos con ese mensaje de error. Hemos construido un número considerable de heurísticas en el algoritmo de informe de errores que intenta deducir el error más "fundamental" que el usuario podría tomar para corregir el error. Nosotros razonamos:

  • El error real es que el conjunto candidato estaba vacío. ¿Por qué el candidato fue puesto en blanco?

  • Porque solo había un método en el grupo de métodos y la inferencia de tipo falló.

De acuerdo, ¿deberíamos informar el error "falló la resolución de sobrecarga porque la inferencia de tipo de método falló"? Una vez más, los clientes no estarían contentos con eso. En cambio, volvemos a preguntar: "¿por qué falló la inferencia del tipo de método?"

  • Porque el conjunto atado de R estaba vacío.

Eso es un error muy malo también. ¿Por qué los límites están vacíos?

  • Porque el único argumento desde el que podríamos determinar R era un lambda cuyo tipo de devolución no podía inferirse.

De acuerdo, ¿deberíamos informar el error "falló la resolución de sobrecarga porque la inferencia de tipo de retorno lambda no pudo inferir un tipo de retorno"? Una vez más , los clientes no estarían contentos con eso. En su lugar, hacemos la pregunta "¿por qué la lambda no pudo inferir un tipo de retorno?"

  • Porque el Cliente no tiene un miembro llamado FristNmae.

Y ese es el error que realmente informamos.

Entonces ves la cadena de razonamiento absolutamente tortuosa que tenemos que pasar para dar el mensaje de error que deseas. No podemos decir simplemente qué fue lo que salió mal, que a la resolución de sobrecarga se le dio un conjunto de candidatos vacío, tenemos que volver al pasado para determinar cómo la resolución de sobrecarga entró en ese estado.

El código que lo hace es extremadamente complejo ; se trata de situaciones más complicadas que la que acabo de presentar, incluidos casos en los que hay n métodos genéricos diferentes y la inferencia de tipo falla por m diferentes razones y tenemos que calcular de entre todos ellos cuál es la "mejor" razón para dar el usuario. Recuerde que, en realidad, hay una docena de tipos diferentes de Select y que la resolución de sobrecarga en todos ellos puede fallar por diferentes razones o por la misma razón.

Hay heurística en los informes de errores del compilador para tratar todo tipo de fallas de resolución de sobrecarga; el que describí es solo uno de ellos.

Entonces, veamos su caso particular. ¿Cuál es el error real?

  • Tenemos un grupo de métodos con un solo método, Foo. ¿Podemos construir un conjunto de candidatos?

  • Sí. Hay un candidato El método Foo es un candidato para la llamada porque tiene todos los parámetros requeridos (barra) y no tiene parámetros adicionales.

  • OK, el conjunto de candidatos tiene un único método. ¿Hay un miembro aplicable del conjunto de candidatos?

  • No. El argumento correspondiente a bar no se puede convertir al tipo de parámetro formal porque el cuerpo lambda contiene un error.

  • Por lo tanto, el conjunto de candidatos aplicable está vacío y, por lo tanto, no hay un mejor candidato válido finalmente válido y, por lo tanto, falla la resolución de sobrecarga.

Entonces, ¿cuál debería ser el error? Nuevamente, no podemos decir simplemente que "la resolución de sobrecarga no pudo encontrar un candidato mejor validado finalmente aceptado" porque los clientes nos odiarían. Tenemos que comenzar a buscar el mensaje de error. ¿Por qué falló la resolución de sobrecarga?

  • Porque el conjunto candidato aplicable estaba vacío.

¿Por qué estaba vacío?

  • Porque todos los candidatos en él fueron rechazados.

¿Hubo un mejor candidato posible?

  • Sí, solo había un candidato.

¿Por qué fue rechazado?

  • Porque su argumento no era convertible al tipo de parámetro formal.

OK, en este punto aparentemente la heurística que maneja los problemas de resolución de sobrecarga que involucran argumentos con nombre decide que hemos excavado lo suficiente y que este es el error que debemos reportar. Si no tenemos argumentos con nombre entonces alguna otra heurística pregunta:

¿Por qué el argumento no fue convertible?

  • Porque el cuerpo lambda contenía un error.

Y luego informamos ese error.

Los errores heurísticos no son perfectos ; lejos de ahi. Casualmente estoy esta semana haciendo una gran rearchición del "simple" error de resolución de sobrecarga reportando heurística - solo cosas como cuándo decir "no hubo un método que tomó 2 parámetros" y cuándo decir "el método que desea es privado" "y cuándo decir" no hay ningún parámetro que corresponda a ese nombre ", y así sucesivamente; Es muy posible que esté llamando a un método con dos argumentos, no hay métodos públicos de ese nombre con dos parámetros, hay uno que es privado pero uno de ellos tiene un argumento con nombre que no coincide. Rápido, ¿qué error deberíamos informar? Tenemos que hacer una mejor suposición, y a veces hay una mejor suposición de que podríamos haber hecho, pero no fuimos lo suficientemente sofisticados para hacer.

Incluso obtener ese derecho está demostrando ser un trabajo muy complicado. Cuando finalmente tengamos que reorganizar la gran heurística de trabajo pesado, por ejemplo, cómo lidiar con las fallas de la inferencia del tipo de método dentro de las expresiones LINQ, revisaré su caso y veremos si podemos mejorar la heurística.

Pero dado que el mensaje de error que está recibiendo es completamente correcto , esto no es un error en el compilador; más bien, es simplemente una deficiencia de la heurística de informe de errores en un caso particular.

Compila este simple programa:

class Program { static void Foo( Action bar ) { bar(); } static void Main( string[] args ) { Foo( () => Console.WriteLine( "42" ) ); } }

Nada extraño allí. Si cometemos un error en el cuerpo de la función lambda:

Foo( () => Console.LineWrite( "42" ) );

el compilador devuelve un mensaje de error:

error CS0117: ''System.Console'' does not contain a definition for ''LineWrite''

Hasta aquí todo bien. Ahora, usemos un parámetro con nombre en la llamada a Foo :

Foo( bar: () => Console.LineWrite( "42" ) );

Esta vez, los mensajes del compilador son algo confusos:

error CS1502: The best overloaded method match for ''CA.Program.Foo(System.Action)'' has some invalid arguments error CS1503: Argument 1: cannot convert from ''lambda expression'' to ''System.Action''

¿Que esta pasando? ¿Por qué no informa el error real ?

Tenga en cuenta que obtenemos el mensaje de error correcto si utilizamos un método anónimo en lugar de la lambda:

Foo( bar: delegate { Console.LineWrite( "42" ); } );


EDITAR: la respuesta de Eric Lippert describe ( mucho mejor) el problema. Consulte su respuesta para el "trato real"

EDICIÓN FINAL: Por poco halagador que sea para uno dejar una demostración pública de su propia ignorancia en la naturaleza, no hay ganancia en ocultar la ignorancia detrás de presionar el botón Eliminar. Espero que alguien más se pueda beneficiar de mi respuesta quijotesca :)

Gracias Eric Lippert y svick por ser paciente y amablemente corregir mi comprensión defectuosa!

El motivo por el que aparece el mensaje de error "incorrecto" es debido a la varianza y la inferencia del compilador de tipos combinados con la forma en que el compilador maneja la resolución de tipo de los parámetros nombrados

El tipo del ejemplo principal () => Console.LineWrite( "42" )

A través de la magia de inferencia de tipo y covarianza, esto tiene el mismo resultado final que

Foo( bar: delegate { Console.LineWrite( "42" ); } );

El primer bloque podría ser de tipo LambdaExpression o delegate ; cuál es depende del uso y de la inferencia.

Dado esto, ¿no es de extrañar que el compilador se confunda cuando le pasa un parámetro que se supone que es una Action pero que podría ser un objeto covariante de un tipo diferente? El mensaje de error es la clave principal que apunta a que la resolución de tipo es el problema.

Echemos un vistazo a la IL para obtener más pistas: Todos los ejemplos dados compilan esto en LINQPad:

IL_0000: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_0005: brtrue.s IL_0018 IL_0007: ldnull IL_0008: ldftn UserQuery.<Main>b__0 IL_000E: newobj System.Action..ctor IL_0013: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_0018: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_001D: call UserQuery.Foo Foo: IL_0000: ldarg.0 **IL_0001: callvirt System.Action.Invoke** IL_0006: ret <Main>b__0: IL_0000: ldstr "42" IL_0005: call System.Console.WriteLine IL_000A: ret

Tenga en cuenta el ** alrededor de la llamada a System.Action.Invoke : callvirt es exactamente lo que parece: una llamada de método virtual.

Cuando llama a Foo con un argumento con nombre, le está diciendo al compilador que está pasando una Action , cuando lo que realmente está pasando es una LambdaExpression . Normalmente, esto está compilado (observe el CachedAnonymousMethodDelegate1 en el IL llamado después del ctor para Action ) a una Action , pero como explícitamente le dijo al compilador que estaba pasando una acción, intenta usar el LambdaExpression pasado como una Action , en lugar de tratándolo como una expresión!

Corto: la resolución de parámetros nombrados falla debido al error en la expresión lambda (que es una falla grave en sí misma)

Aquí está el otro decir:

Action b = () => Console.LineWrite("42"); Foo(bar: b);

produce el mensaje de error esperado.

Probablemente no soy 100% preciso en algunas de las cosas de IL, pero espero haber transmitido la idea general

EDITAR: dlev hizo un gran punto en los comentarios del OP sobre el orden de la resolución de sobrecarga también jugando un papel.


Nota: No es realmente una respuesta, pero es demasiado grande para un comentario.

Resultados más interesantes cuando arrojas inferencias de tipo. Considera este código:

public class Test { public static void Blah<T>(Action<T> blah) { } public static void Main() { Blah(x => { Console.LineWrite(x); }); } }

No compilará, porque no hay una buena manera de inferir qué T debería ser.
Mensaje de error :

Los argumentos de tipo para el método ''Test.Blah<T>(System.Action<T>)'' no se pueden deducir del uso. Intente especificar los argumentos de tipo explícitamente.

Tiene sentido. Especifiquemos el tipo de x explícitamente y veamos qué sucede:

public static void Main() { Blah((int x) => { Console.LineWrite(x); }); }

Ahora las cosas van mal porque LineWrite no existe.
Mensaje de error :

''System.Console'' no contiene una definición para ''LineWrite''

También sensato. Ahora agreguemos argumentos con nombre y veamos qué sucede. Primero, sin especificar el tipo de x :

public static void Main() { Blah(blah: x => { Console.LineWrite(x); }); }

Esperamos recibir un mensaje de error acerca de no poder inferir los argumentos de tipo. Y lo hacemos Pero eso no es todo .
Mensajes de error :

Los argumentos de tipo para el método ''Test.Blah<T>(System.Action<T>)'' no se pueden deducir del uso. Intente especificar los argumentos de tipo explícitamente.

''System.Console'' no contiene una definición para ''LineWrite''

Ordenado. La inferencia de tipo falla y se nos dice exactamente por qué falló la conversión lambda. Bien, entonces especifiquemos el tipo de x y veamos qué obtenemos:

public static void Main() { Blah(blah: (int x) => { Console.LineWrite(x); }); }

Mensajes de error :

Los argumentos de tipo para el método ''Test.Blah<T>(System.Action<T>)'' no se pueden deducir del uso. Intente especificar los argumentos de tipo explícitamente.

''System.Console'' no contiene una definición para ''LineWrite''

Ahora eso es inesperado. La inferencia de tipo todavía está fallando (supongo que porque la conversión lambda -> Action<T> está fallando, anulando así la suposición del compilador de que T es int ) e informando la causa de la falla.

TL; DR : Me alegraré cuando Eric Lippert revise la heurística de estos casos más complejos.