2009-09-02 28 views
125

¿Por qué no se puede usar un parámetro ref o out en una expresión lambda?No se puede usar el parámetro ref o out en las expresiones lambda

Me encontré con el error hoy y encontré una solución, pero todavía tenía curiosidad de por qué este es un error en tiempo de compilación.

Aquí está un ejemplo sencillo:

private void Foo() 
{ 
    int value; 
    Bar(out value); 
} 

private void Bar(out int value) 
{ 
    value = 3; 
    int[] array = { 1, 2, 3, 4, 5 }; 
    int newValue = array.Where(a => a == value).First(); 
} 
+10

puedo preguntar cuál era la solución que había encontró ? – Beatles1692

+0

Se trata de iteradores, pero gran parte del mismo razonamiento en esta publicación (también por Eric Lippert — él está en el equipo de diseño de idiomas) se aplica a lambdas:

Respuesta

94

Lambdas tienen la apariencia de cambiar la duración de las variables que capturan. Por ejemplo, la siguiente expresión lambda hace que el p1 parámetro para vivo más largo que el marco método actual como su valor se puede acceder después de la trama método ya no en la pila

Func<int> Example(int p1) { 
    return() => p1; 
} 

Otra propiedad de las variables capturadas es que es los cambios en la variable también son visibles fuera de la expresión lambda. Por ejemplo los siguientes impresiones 42

void Example2(int p1) { 
    Action del =() => { p1 = 42; } 
    del(); 
    Console.WriteLine(p1); 
} 

Estas dos propiedades producen un cierto conjunto de efectos que van en contra de un parámetro ref de las siguientes maneras

  • parámetros ref pueden tener una vida fija. Considere pasar una variable local como un parámetro de referencia a una función.
  • Los efectos secundarios en el lambda tendrían que ser visibles en el propio parámetro ref. Tanto dentro del método como en la persona que llama.

Estas son propiedades algo incompatibles y son una de las razones por las que no se permiten en expresiones lambda.

+6

Entiendo que no podemos usar 'ref' dentro de la expresión lambda, pero el deseo de usarlo no se ha alimentado . – zionpi

66

Bajo el capó, el método anónimo es implementado por izar las variables capturadas (que es lo que su cuerpo cuestión tiene que ver) y almacenarlas como campos de un compilador clase generada No hay forma de almacenar un parámetro ref o out como un campo. Eric Lippert lo discutió en a blog entry. Tenga en cuenta que existe una diferencia entre las variables capturadas y los parámetros lambda. Usted puede tener "parámetros formales" como el siguiente, ya que no son capturados variables:

delegate void TestDelegate (out int x); 
static void Main(string[] args) 
{ 
    TestDelegate testDel = (out int x) => { x = 10; }; 
    int p; 
    testDel(out p); 
    Console.WriteLine(p); 
} 
5

Este es uno de los primeros resultados de "C# lambda ref" en Google; Siento que necesito expandir las respuestas anteriores. La sintaxis de delegado anónimo más antigua (C# 2.0) funciona y admite firmas más complejas (así como cierres). Por lo menos, los delegados de Lambda y anónimos han compartido la implementación percibida en el backend del compilador (si no son idénticos), y lo más importante, admiten los cierres.

Lo que estaba tratando de hacer cuando lo hice la búsqueda, para demostrar la sintaxis:

public static ScanOperation<TToken> CreateScanOperation(
    PrattTokenDefinition<TNode, TToken, TParser, TSelf> tokenDefinition) 
{ 
    var oldScanOperation = tokenDefinition.ScanOperation; // Closures still work. 
    return delegate(string text, ref int position, ref PositionInformation currentPosition) 
     { 
      var token = oldScanOperation(text, ref position, ref currentPosition); 
      if (token == null) 
       return null; 
      if (tokenDefinition.LeftDenotation != null) 
       token._led = tokenDefinition.LeftDenotation(token); 
      if (tokenDefinition.NullDenotation != null) 
       token._nud = tokenDefinition.NullDenotation(token); 
      token.Identifier = tokenDefinition.Identifier; 
      token.LeftBindingPower = tokenDefinition.LeftBindingPower; 
      token.OnInitialize(); 
      return token; 
     }; 
} 

Hemos de tener en cuenta que Lambdas son procesal y matemáticamente más seguro (a causa de la promoción valor de referencia se mencionó anteriormente): puedes abrir una lata de gusanos. Piense con cuidado cuando use esta sintaxis.

+3

Creo que malinterpretaste la pregunta. La pregunta era por qué una lambda no podía acceder a las variables ref/out en su método contenedor, no por qué la _lambda en sí misma no puede contener variables ref/out. AFAIK no hay una buena razón para esto último. Hoy escribí una lambda '(a, b, c, ref d) => {...}' y 'ref' se subrayó en rojo con el mensaje de error" El parámetro '4' debe declararse con la palabra clave 'ref' ". Facepalm! PD ¿Qué es "promoción de valor de ref"? – Qwertie

+1

@Qwertie Lo hice funcionar con la parametrización completa, es decir, incluir los tipos en a, b, c y d, y funciona. Vea la respuesta de BenAdams (aunque él también malinterpreta la pregunta original). –

+0

@Qwertie Creo que solo eliminé la mitad de ese punto. Creo que el punto original fue que colocar los parametros en un cierre podría ser arriesgado, pero posteriormente me di cuenta de que esto no estaba sucediendo en el ejemplo que dí (y ni sé si eso incluso compilará). –

39

Puede, pero debe definir explícitamente todos los tipos sin embargo así

(a, b, c, ref d) => {...} 

no es válido,

(int a, int b, int c, ref int d) => {...} 

Es válida

+3

Aunque es interesante, esto no responde la pregunta. Intente leerlo de nuevo – edc65

+8

Sí; la pregunta es por qué no puedes; respuesta es usted puede –

+11

No; La pregunta es por qué no se puede hacer referencia a una * variable existente *, ya definida como 'ref' o' out', dentro de una lambda. Está claro si lee el código de ejemplo (intente leerlo nuevamente). La respuesta aceptada explica claramente por qué. Su respuesta es acerca de usar 'ref' o' out' * parameter * para la lambda. Totalmente no responder la pregunta y hablar sobre otra cosa – edc65

Cuestiones relacionadas