Não é possível usar o parâmetro ref ou out nas expressões lambda

173

Por que você não pode usar um parâmetro ref ou out em uma expressão lambda?

Encontrei o erro hoje e encontrei uma solução alternativa, mas ainda estava curioso por que esse é um erro em tempo de compilação.

CS1628 : Não é possível usar no parâmetro ref ou out 'parameter' dentro de um método anônimo, expressão lambda ou expressão de consulta

Aqui está um exemplo simples:

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
fonte
É sobre iteradores, mas o mesmo raciocínio deste post (também de Eric Lippert & mdash; ele faz parte da equipe de design de idiomas) se aplica a lambdas: < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn
17
Posso perguntar qual foi a solução alternativa que você encontrou?
Beatles1692
3
Você pode simplesmente declarar uma variável normal local e trabalhar com isso, e atribuir o resultado ao valor posteriormente ... Adicione um var tempValue = value; e trabalhe com tempValue.
Drunken Code Monkey:

Respostas:

122

Os lambdas parecem alterar o tempo de vida das variáveis ​​que eles capturam. Por exemplo, a seguinte expressão lambda faz com que o parâmetro p1 viva mais que o quadro de método atual, pois seu valor pode ser acessado depois que o quadro de método não estiver mais na pilha

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

Outra propriedade das variáveis ​​capturadas é que as alterações na variável também são visíveis fora da expressão lambda. Por exemplo, as seguintes impressões 42

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

Essas duas propriedades produzem um certo conjunto de efeitos que voam na face de um parâmetro ref das seguintes maneiras

  • Os parâmetros ref podem ter uma vida útil fixa. Considere passar uma variável local como um parâmetro ref para uma função.
  • Os efeitos colaterais no lambda precisariam ser visíveis no próprio parâmetro ref. Tanto no método como no chamador.

Essas são propriedades um tanto incompatíveis e são uma das razões pelas quais elas não são permitidas nas expressões lambda.

JaredPar
fonte
36
Entendo que não podemos usar a refexpressão lambda dentro, mas o desejo de usá-la não foi alimentado.
Zionpi
85

Sob o capô, o método anônimo é implementado ao içar variáveis ​​capturadas (que é o objetivo do seu corpo de perguntas) e armazená-las como campos de uma classe gerada pelo compilador. Não há como armazenar um refou outparâmetro como um campo. Eric Lippert discutiu isso em uma entrada de blog . Observe que há uma diferença entre variáveis ​​capturadas e parâmetros lambda. Você pode ter "parâmetros formais" como os seguintes, pois não são variáveis ​​capturadas:

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
fonte
70

Você pode, mas deve definir explicitamente todos os tipos para

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

É inválido, no entanto

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

É válido

Ben Adams
fonte
13
Faz; pergunta é por que você não pode; A resposta é que você pode.
Ben Adams
24
Não faz; A questão é por que você não pode fazer referência a uma variável existente , já definida refou outdentro de uma lambda. É claro se você ler o código de exemplo (tente novamente para lê-lo novamente). A resposta aceita explica claramente o porquê. Sua resposta é sobre o uso refou out parâmetro para o lambda. Totalmente não responder a questão e falar sobre outra coisa
edc65
4
@ edc65 está certo ... isso não tem nada a ver com o assunto da pergunta, que é sobre o conteúdo da expressão lamba (à direita), não sua lista de parâmetros (à esquerda). É bizarro que isso tenha 26 votos positivos.
Jim Balter
6
Isso me ajudou. +1 para isso. Graças
Emad
1
Mas ainda não entendo por que foi projetado para ser assim. Por que tenho que definir explicitamente todos os tipos? Semântica eu não preciso. Estou perdendo alguma coisa?
joe
5

Como esse é um dos principais resultados para "C # lambda ref" no Google; Sinto que preciso expandir as respostas acima. A sintaxe de delegado anônimo mais antiga (C # 2.0) funciona e suporta assinaturas mais complexas (além de fechamentos). Os delegados anônimos e da Lambda, no mínimo, compartilharam a implementação percebida no back-end do compilador (se não forem idênticos) - e o mais importante, eles suportam fechamentos.

O que eu estava tentando fazer quando fiz a pesquisa, para demonstrar a sintaxe:

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;
        };
}

Lembre-se de que as Lambdas são processualmente e matematicamente mais seguras (devido à promoção do valor de referência mencionada anteriormente): você pode abrir uma lata de worms. Pense com cuidado ao usar esta sintaxe.

Jonathan Dickinson
fonte
3
Eu acho que você não entendeu a pergunta. A questão era por que um lambda não podia acessar variáveis ​​ref / out em seu método de contêiner, não por que o próprio lambda não pode conter variáveis ​​ref / out. AFAIK não há uma boa razão para este último. Hoje eu escrevi um lambda (a, b, c, ref d) => {...}e reffui sublinhado em vermelho com a mensagem de erro "O parâmetro '4' deve ser declarado com a palavra-chave 'ref'". Facepalm! PS o que é "promoção de valor ref"?
Qwertie
1
@ Qwertie Consegui que isso funcionasse com parametrização completa, ou seja, inclua os tipos em a, b, ce ed e funciona. Veja a resposta de BenAdams (embora ele também entenda mal a pergunta original).
Ed Bayiates
@ Qwertie Acho que removi apenas metade desse ponto - acho que o ponto original era que colocar params ref em um fechamento pode ser arriscado, mas devo ter percebido posteriormente que isso não estava acontecendo no exemplo que eu dei (e nem Eu sei se isso compilaria).
23416 Jonathan Dickinson
Isso não tem nada a ver com a pergunta realmente feita ... veja a resposta aceita e os comentários sob a resposta de Ben Adams, que também não entendeu a pergunta.
Jim Balter
1

E talvez isso?

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();
}
cócegas
fonte