2011-02-11 16 views
116

Tengo el siguiente código:¿Por qué no se puede asignar un método anónimo a var?

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é no puede la figura compilador cabo se trata de un Func<string, bool>? Toma un parámetro de cadena y devuelve un booleano. En su lugar, me da el error:

Cannot assign anonymous method to an implicitly-typed local variable.

tengo una conjetura y que es si la versión var compilado, que carecería de consistencia si tuviera el siguiente:

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

El wouldn anteriormente No tiene 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.

+3

Nota sobre su argumento * 4 arguments *, en .NET 4, 'Func <>' acepta hasta 16 argumentos. –

+0

Gracias por la aclaración. Estoy usando .NET 3.5. – Marlon

+9

¿Por qué haría el compilador pensar que es un 'Func '? ¡Parece un 'Converter ' para mí! –

Respuesta

130

Otros ya han señalado que hay un número infinito de posibles tipos de delegados que podría haber significado; ¿Qué tiene de especial el Func que merece ser el valor predeterminado en lugar de Predicate o Action o cualquier otra posibilidad? Y, para lambdas, ¿por qué es obvio que la intención es elegir el formulario de delegado, en lugar de la forma de árbol de expresión?

Pero podríamos decir que Func es 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 te gustaría inferir para los siguientes casos?

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

No hay ningún tipo de código Func<T> que realice una referencia.

var x2 = y=>123; 

No conocemos el tipo del parámetro formal, aunque sí sabemos el retorno. (O ¿verdad? Es el retorno int? Tiempo? Corto? Bytes?)

var x3 = (int y)=>null; 

No sabemos el tipo de retorno, pero no podemos estar vacío. El tipo de devolución podría ser cualquier tipo de referencia o cualquier tipo de valor que admite valores.

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

Una vez más, no sabemos el tipo de retorno, y esta vez podemos anuladas.

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

¿Está destinado a ser una declaración de devolución de vacío lambda o algo que devuelve el valor asignado a q? Ambos son legales; ¿Qué deberíamos elegir?

Ahora, puede decir, bueno, simplemente no admite ninguna de esas características. Solo soporte casos "normales" donde los tipos se pueden resolver. Eso no ayuda. ¿Cómo eso me hace la vida más fácil? Si la función funciona a veces y falla a veces, entonces todavía tengo que escribir el código en detectar todas esas situaciones de falla y dar un mensaje de error significativo para cada uno. Todavía tenemos que especificar todo ese comportamiento, documentarlo, escribir pruebas para él, etc. Esta es una característica muy costosa que le ahorra al usuario una 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 no proporciona casi ningún beneficio en los casos en que funciona.

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

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

porque no hay ningún tipo de "decible" para esa cosa. Pero tenemos este problema todo el tiempo, y sólo tiene que utilizar el método de la inferencia 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 trabaja tipo de método de inferencia qué tipo func es.

+3

+1 Gran respuesta. Con todos llamando tu nombre, sabíamos que aparecerías. –

+34

¿Cuándo va a compilar sus respuestas SO en un libro? Lo compraría :) –

+12

En segundo lugar la propuesta de un libro de respuestas de Eric Lippert. Título sugerido: "Reflections from the Stack" –

4

Diferentes delegados se consideran tipos diferentes. Por ejemplo, Action es diferente de MethodInvoker, y una instancia de Action no se puede asignar a una variable de tipo MethodInvoker.

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

mismo modo, si declaro un tipo de delegado teniendo un argumento string y devolviendo una bool, ¿cómo el compilador sabe que realmente quería un Func<string, bool> en lugar de mi tipo de delegado? No puede inferir el tipo de delegado.

27

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

Tenga en cuenta su ejemplo:

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

Aquí hay dos posibles inferencias para lo que el var deberí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 debe inferir el compilador? No hay una buena razón para elegir uno u otro. Y aunque un Predicate<T> es funcionalmente equivalente a un Func<T, bool>, todavía son tipos diferentes en el nivel del sistema de tipo .NET. Por lo tanto, el compilador no puede resolver inequívocamente el tipo de delegado, y debe fallar la inferencia de tipo.

+1

Estoy seguro bastante algunas otras personas en Microsoft también lo saben con certeza. ;) Pero sí, alude a un motivo 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 implícitamente tipadas. –

+1

Sí. Y lo que es peor, para lambdas ni siquiera sabemos si va a un tipo de delegado; podría ser un árbol de expresión. –

+0

Para cualquier persona interesada, escribí un poco más sobre esto y cómo los enfoques C# y F # contrastan en http://www.mindscapehq.com/blog/index.php/2011/02/23/first-class-functions- in-f-part-0/ – itowlson

6

Eric Lippert tiene una vieja post al respecto en el que dice

And in fact the C# 2.0 specification calls this out. Method group expressions and anonymous method expressions are typeless expressions in C# 2.0, and lambda expressions join them in C# 3.0. Therefore it is illegal for them to appear "naked" on the right hand side of an implicit declaration.

+0

Y esto está subrayado por la sección 8.5.1 de la especificación del lenguaje. "La expresión del inicializador debe tener un tipo de tiempo de compilación" para poder usarse en una variable local implícitamente tipada. –

+0

@Anthony: Gracias por la actualización. –

1

Los siguientes puntos son de la MSDN cuanto a las variables locales Implícitamente proporcionado:

  1. var sólo se puede utilizar cuando una variable local se declara e inicializa en la misma declaración; la variable no se puede inicializar a nulo, a un grupo de métodos o a una función anónima.
  2. La palabra clave var ordena al compilador inferir el tipo de la variable de la expresión en el lado derecho de la declaración de inicialización.
  3. Es importante comprender que la palabra clave var no significa "variante" y no indica que la variable esté escrita a máquina de forma imprecisa o encuadernada tarde. Simplemente significa que el compilador determina y asigna el tipo más apropiado.

MSDN Reference: Implicitly Typed Local Variables

Teniendo en cuenta los métodos anónimos siguiente en relación con:

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

MSDN Reference: Anonymous Methods

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

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); 
Cuestiones relacionadas