¿Por qué no se puede asignar un método anónimo a var?

139

Tengo el siguiente código:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

Sin embargo, lo siguiente no se compila:

var comparer = delegate(string value) {
    return value != "0";
};

¿Por qué el compilador no puede entender que es un Func<string, bool>? Toma un parámetro de cadena y devuelve un valor booleano. En cambio, me da el error:

No se puede asignar un método anónimo a una variable local tipada implícitamente.

Tengo una suposición y es que si se compila la versión var , carecería de coherencia si tuviera lo siguiente:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

Lo anterior no tendría sentido ya que Func <> permite solo hasta 4 argumentos (en .NET 3.5, que es lo que estoy usando). Quizás alguien podría aclarar el problema. Gracias.

Marlon
fuente
3
Nota sobre su argumento de 4 argumentos , en .NET 4, Func<>acepta hasta 16 argumentos.
Anthony Pegram
Gracias por la aclaración. Estoy usando .NET 3.5.
Marlon
9
¿Por qué haría el compilador pensar que es un Func<string, bool>? ¡A Converter<string, bool>mí me parece!
Ben Voigt
3
a veces extraño VB ..Dim comparer = Function(value$) value <> "0"
Slai

Respuestas:

155

Otros ya han señalado que hay infinitos tipos de delegados posibles que podría haber querido decir; lo que se trata tan especial Funcque merece ser el defecto en lugar de Predicateo Action, o cualquier otra posibilidad? Y, para lambdas, ¿por qué es obvio que la intención es elegir la forma delegada, en lugar de la forma del árbol de expresión?

Pero podríamos decir que Funces especial, y que el tipo inferido de un método lambda o anónimo es Func de algo. Todavía tendríamos todo tipo de problemas. ¿Qué tipos le gustaría inferir para los siguientes casos?

var x1 = (ref int y)=>123;

No hay ningún Func<T>tipo que tome una referencia de nada.

var x2 = y=>123;

No sabemos el tipo del parámetro formal, aunque sí sabemos el retorno. (¿O sí? ¿El retorno es int? Largo? Corto? Byte?)

var x3 = (int y)=>null;

No sabemos el tipo de devolución, pero no puede ser nulo. El tipo de retorno podría ser cualquier tipo de referencia o cualquier tipo de valor anulable.

var x4 = (int y)=>{ throw new Exception(); }

Nuevamente, no sabemos el tipo de retorno, y esta vez puede ser nulo.

var x5 = (int y)=> q += y;

¿Se pretende que sea una declaración lambda que devuelva el vacío o algo que devuelva el valor asignado a q? Ambos son legales; ¿Cuál deberíamos elegir?

Ahora, podría decir, bueno, simplemente no es compatible con ninguna de esas características. Solo admite casos "normales" donde los tipos pueden ser resueltos Eso no ayuda. ¿Cómo me facilita la vida? Si la función funciona a veces y falla a veces, aún tengo que escribir el código para detectar todas esas situaciones de falla y dar un mensaje de error significativo para cada una. Todavía tenemos que especificar todo ese comportamiento, documentarlo, escribir pruebas para ello, etc. Esta es una característica muy costosa que ahorra al usuario tal vez media docena de pulsaciones de teclas. Tenemos mejores formas de agregar valor al lenguaje que pasar mucho tiempo escribiendo casos de prueba para una función que no funciona la mitad del tiempo y que apenas proporciona ningún beneficio en los casos en que sí funciona.

La situación en la que es realmente útil es:

var xAnon = (int y)=>new { Y = y };

porque no hay un tipo "hablable" para esa cosa. Pero tenemos este problema todo el tiempo, y solo usamos la inferencia de tipo de método para deducir el tipo:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

y ahora la inferencia de tipo de método determina cuál es el tipo de función.

Eric Lippert
fuente
43
¿Cuándo vas a compilar tus respuestas SO en un libro? Lo compraría :)
Matt Greer
13
Secundo la propuesta de un libro de Eric Lippert de respuestas SO. Título sugerido: "Reflexiones de la pila"
Adam Rackis
24
@Eric: Buena respuesta, pero es un poco engañoso ilustrar esto como algo que no es posible, ya que en realidad funciona completamente bien en D. Es solo que ustedes no eligieron dar a los literales delegados su propio tipo, y en su lugar los hicieron depender en sus contextos ... así que en mi humilde opinión la respuesta debería ser "porque así lo hicimos" más que cualquier otra cosa. :)
user541686
55
@abstractdissonance También noto que el compilador es de código abierto. Si le interesa esta función, puede donar el tiempo y el esfuerzo necesarios para que esto suceda. Te animo a enviar una solicitud de extracción.
Eric Lippert
77
@AbstractDissonance: medimos el costo en términos de recursos limitados: desarrolladores y tiempo. Esta responsabilidad no fue otorgada por dios; fue impuesta por el vicepresidente de la división de desarrolladores. La noción de que de alguna manera el equipo de C # podría ignorar un proceso presupuestario es extraña. Les aseguro que las compensaciones fueron y siguen siendo hechas por la consideración cuidadosa y reflexiva de los expertos que tenían deseos expresados ​​por las comunidades de C #, la misión estratégica de Microsoft y su excelente gusto por el diseño.
Eric Lippert
29

Solo Eric Lippert lo sabe con certeza, pero creo que es porque la firma del tipo delegado no determina de forma exclusiva el tipo.

Considera tu ejemplo:

var comparer = delegate(string value) { return value != "0"; };

Aquí hay dos posibles inferencias de lo que vardebería ser:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

¿Cuál debería inferir el compilador? No hay una buena razón para elegir uno u otro. Y aunque a Predicate<T>es funcionalmente equivalente a a Func<T, bool>, todavía son tipos diferentes a nivel del sistema de tipos .NET. Por lo tanto, el compilador no puede resolver inequívocamente el tipo de delegado y debe fallar la inferencia de tipo.

itowlson
fuente
1
Estoy seguro de que muchas otras personas en Microsoft también lo saben con certeza. ;) Pero sí, alude a una razón principal, el tipo de tiempo de compilación no se puede determinar porque no hay ninguno. La Sección 8.5.1 de la especificación del lenguaje destaca específicamente esta razón para no permitir que las funciones anónimas se utilicen en declaraciones de variables escritas implícitamente.
Anthony Pegram
3
Sí. Y lo que es peor, para lambdas ni siquiera sabemos si va a un tipo delegado; Puede ser un árbol de expresión.
Eric Lippert
Para cualquier persona interesada, escribí un poco más sobre esto y cómo los enfoques de C # y F # contrastan en mindscapehq.com/blog/index.php/2011/02/23/…
itowlson
por qué no puede el compilador simplemente fabricar un nuevo tipo único como C ++ hace por su función lambda
Weipeng L
¿En qué se diferencian "a nivel del sistema de tipos .NET"?
arao6
6

Eric Lippert tiene una vieja publicación al respecto donde dice

Y, de hecho, la especificación C # 2.0 lo llama así. Las expresiones de grupo de métodos y las expresiones de método anónimo son expresiones sin tipo en C # 2.0, y las expresiones lambda se unen a ellas en C # 3.0. Por lo tanto, es ilegal que aparezcan "desnudos" en el lado derecho de una declaración implícita.

Brian Rasmussen
fuente
Y esto se subraya en la sección 8.5.1 de la especificación del lenguaje. "La expresión inicializadora debe tener un tipo de tiempo de compilación" para ser utilizada para una variable local tipada implícitamente.
Anthony Pegram
5

Los diferentes delegados se consideran diferentes tipos. por ejemplo, Actiones diferente de MethodInvoker, y una instancia de Actionno se puede asignar a una variable de tipo MethodInvoker.

Entonces, dado un delegado anónimo (o lambda) como () => {}, ¿es un Actiono un MethodInvoker? El compilador no puede decirlo.

Del mismo modo, si declaro que un tipo de delegado toma un stringargumento y devuelve un bool, ¿cómo sabría el compilador que realmente desea un Func<string, bool>tipo de delegado? No puede inferir el tipo de delegado.

Stephen Cleary
fuente
2

Los siguientes puntos son del MSDN con respecto a las variables locales escritas implícitamente:

  1. var solo se puede usar cuando una variable local se declara e inicializa en la misma instrucción; la variable no se puede inicializar en nulo, o en un grupo de métodos o una función anónima.
  2. La palabra clave var indica al compilador que infiera el tipo de la variable de la expresión en el lado derecho de la instrucción de inicialización.
  3. Es importante comprender que la palabra clave var no significa "variante" y no indica que la variable esté tipeada o limitada. Simplemente significa que el compilador determina y asigna el tipo más apropiado.

Referencia de MSDN: Variables locales escritas implícitamente

Teniendo en cuenta lo siguiente con respecto a los métodos anónimos:

  1. Los métodos anónimos le permiten omitir la lista de parámetros.

Referencia de MSDN: métodos anónimos

Sospecharía que, dado que el método anónimo puede tener firmas de métodos diferentes, el compilador no puede inferir correctamente cuál sería el tipo más apropiado para asignar.

nybbler
fuente
1

Mi publicación no responde la pregunta real, pero sí responde la pregunta subyacente de:

"¿Cómo evito tener que escribir algún tipo fugoso como Func<string, string, int, CustomInputType, bool, ReturnType>?"[1]

Siendo el programador perezoso / hacky que soy, experimenté usando Func<dynamic, object> , que toma un solo parámetro de entrada y devuelve un objeto.

Para múltiples argumentos, puede usarlo así:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

Consejo: puedes usar Action<dynamic> si no necesita devolver un objeto.

Sí, sé que probablemente va en contra de tus principios de programación, pero esto tiene sentido para mí y probablemente para algunos codificadores de Python.

Soy bastante novato en los delegados ... solo quería compartir lo que aprendí.


[1] Esto supone que no está llamando a un método que requiere Funcun parámetro predefinido , en cuyo caso, tendrá que escribir esa cadena fugly: /

Ambrose Leung
fuente
0

¿Qué tal eso?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);
mmm
fuente