No se puede usar el parámetro ref o out en expresiones lambda

173

¿Por qué no puedes usar un parámetro ref o out en una expresión lambda?

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

CS1628 : No se puede usar el parámetro 'parámetro' ref o out dentro de un método anónimo, expresión lambda o expresión de consulta

Aquí hay un ejemplo simple:

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();
}
skalb
fuente
Se trata de iteradores, pero gran parte del mismo razonamiento en esta publicación (también de Eric Lippert y mdash; él está en el equipo de diseño de lenguaje después de todo) se aplica a lambdas: < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn
17
¿Puedo preguntar cuál fue la solución que encontraste?
Beatles1692
3
Simplemente puede declarar una variable normal local y trabajar con eso, y asignar el resultado al valor después ... Agregue un var tempValue = value; y luego trabaje con tempValue.
Drunken Code Monkey

Respuestas:

122

Las lambdas tienen la apariencia de cambiar la vida útil de las variables que capturan. Por ejemplo, la siguiente expresión lambda hace que el parámetro p1 viva más tiempo que el marco del método actual ya que se puede acceder a su valor una vez que el marco del método ya no está en la pila

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

Otra propiedad de las variables capturadas es que los cambios en la variable también son visibles fuera de la expresión lambda. Por ejemplo, las 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 se enfrentan a un parámetro de referencia de las siguientes maneras

  • Los parámetros de referencia pueden tener una vida útil fija. Considere pasar una variable local como parámetro de referencia a una función.
  • Los efectos secundarios en el lambda tendrían que ser visibles en el 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 están permitidas en las expresiones lambda.

JaredPar
fuente
36
Entiendo que no podemos usar refdentro de la expresión lambda, pero el deseo de usarlo no ha sido alimentado.
zionpi
85

Bajo el capó, el método anónimo se implementa al izar variables capturadas (que es de lo que se trata su cuerpo de preguntas) y almacenarlas como campos de una clase generada por el compilador. No hay forma de almacenar un parámetro refo outcomo un campo. Eric Lippert lo discutió en una entrada de blog . 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 son variables no capturados:

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);
}
Mehrdad Afshari
fuente
70

Puede, pero debe definir explícitamente todos los tipos para

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

Sin embargo, es inválido

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

Es válido

Ben Adams
fuente
13
Lo hace; la pregunta es por qué no puedes; La respuesta es que puedes.
Ben Adams
24
No lo hace; La pregunta es por qué no puede hacer referencia a una variable existente , ya definida refo 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 sobre el uso refo out parámetro de la lambda. Totalmente sin responder la pregunta y hablando de otra cosa
edc65
44
@ edc65 tiene razón ... esto no tiene nada que ver con el tema de la pregunta, que trata sobre el contenido de la expresión de lamba (a la derecha), no su lista de parámetros (a la izquierda). Es extraño que esto tenga 26 votos a favor.
Jim Balter
66
Sin embargo, me ayudó. +1 por eso. Gracias
Emad
1
Pero todavía no entiendo por qué ha sido diseñado para ser así. ¿Por qué tengo que definir explícitamente todos los tipos? Semánticamente no necesito hacerlo. ¿Estoy perdiendo algo?
Joe
5

Como este es uno de los mejores resultados para "C # lambda ref" en Google; Siento que necesito ampliar 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). Los delegados anónimos y de Lambda, como mínimo, han compartido la implementación percibida en el backend del compilador (si no son idénticos), y lo más importante, apoyan los cierres.

Lo que estaba tratando de hacer cuando 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;
        };
}

Solo tenga en cuenta que Lambdas es más seguro desde el punto de vista matemático y de procedimiento (debido a la promoción del valor de referencia mencionado anteriormente): puede abrir una lata de gusanos. Piensa cuidadosamente cuando uses esta sintaxis.

Jonathan Dickinson
fuente
3
Creo que entendiste mal la pregunta. La pregunta era por qué un lambda no podía acceder a las variables de ref / out en su método contenedor, no por qué el lambda en sí no puede contener variables de ref / out. AFAIK no hay una buena razón para esto último. Hoy escribí una lambda (a, b, c, ref d) => {...}y refestaba subrayado 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 referencia"?
Qwertie
1
@Qwertie Obtuve esto para trabajar con la parametrización completa, es decir, incluir los tipos en a, b, c y d y funciona. Vea la respuesta de BenAdams (aunque también entiende mal la pregunta original).
Ed Bayiates
@Qwertie Creo que solo eliminé la mitad de ese punto: creo que el punto original era que colocar parámetros de referencia en un cierre podría ser arriesgado, pero posteriormente me di cuenta de que esto no estaba sucediendo en el ejemplo que di (y tampoco Sé si eso incluso compilaría).
Jonathan Dickinson el
Esto no tiene nada que ver con la pregunta que realmente se hizo ... vea la respuesta aceptada y los comentarios bajo la respuesta de Ben Adams, quien también entendió mal la pregunta.
Jim Balter
1

Y tal vez esto?

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

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