Pergunta

Eu tenho o seguinte código:

string prefix = "OLD:";
Func<string, string> prependAction = (x => prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Como o compilador substitui a variável de prefixo com um fecho "NOVO: brownie". É impresso para o console

Existe uma maneira fácil de impedir que o compilador de levantar a variável de prefixo enquanto ainda fazendo uso de uma expressão lambda? Eu gostaria de ter uma maneira de fazer o meu trabalho Func de forma idêntica para:

Func<string, string> prependAction = (x => "OLD:" + x);

A razão que eu preciso é que eu gostaria de serializar o delegado resultante. Se a variável de prefixo está numa classe não-seriável a função acima não serializará.

A única maneira de contornar isso eu posso ver neste momento é criar uma nova classe serializável que armazena a seqüência como uma variável de membro e tem o método seqüência anteposta:

string prefix = "NEW:";
var prepender = new Prepender {Prefix = prefix};
Func<string, string> prependAction = prepender.Prepend;
prefix = "OLD:";
Console.WriteLine(prependAction("brownie"));

Com classe auxiliar:

[Serializable]
public class Prepender
{
    public string Prefix { get; set; }
    public string Prepend(string str)
    {
        return Prefix + str;
    }
}

Este parece ser um monte de trabalho extra para obter o compilador para ser "burro".

Foi útil?

Solução

Eu vejo o problema subjacente agora. É mais profundo do que eu pensava. Basicamente, a solução é modificar a árvore de expressão antes de serialização-lo, substituindo todas as subárvores que não dependem dos parâmetros com os nós constantes. Este é, aparentemente chamado de "funcletization". Há uma explicação de que aqui .

Outras dicas

Apenas fazer uma outra fechamento ...

Digamos, algo como:

var prepend = "OLD:";

Func<string, Func<string, string>> makePrepender = x => y => (x + y);
Func<string, string> oldPrepend = makePrepender(prepend);

prepend = "NEW:";

Console.WriteLine(oldPrepend("Brownie"));

Havn't testados ainda como eu não tenho acesso a VS no momento, mas, normalmente, é assim que eu resolver tal problema.

Lambdas automaticamente 'sugar' em variáveis ??locais, temo que é simplesmente como eles funcionam, por definição.

Este é um problema muito comum isto é, variáveis ??que está sendo modificado por um fecho involuntariamente - uma solução muito mais simples é apenas para ir:

string prefix = "OLD:";
var actionPrefix = prefix;
Func<string, string> prependAction = (x => actionPrefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Se você estiver usando o ReSharper ele vai realmente identificar os lugares em seu código onde você está em risco de causar efeitos colaterais inesperados como este - por isso, se o arquivo é "tudo verde" o seu código deve ser OK

Eu acho que, de certa forma, teria sido bom se tivéssemos um pouco de açúcar sintático para lidar com esta situação para que pudéssemos ter escrito isso como uma one-liner i.

Func<string, string> prependAction = (x => ~prefix + x);

Onde alguns operador de prefixo causaria o valor da variável a ser avaliado antes de construir o delegado / função anônima.

Eu recebo o problema agora: o lambda refere-se à classe que contém o que pode não ser serializável. Em seguida, fazer algo como isto:

public void static Func<string, string> MakePrependAction(String prefix){
    return (x => prefix + x);
}

(Note a palavra-chave estática.) Em seguida, a lambda não precisa referenciar a classe que contém.

Já existem várias respostas aqui explicando como você pode evitar o lambda "lifting" a sua variável. Infelizmente isso não resolver o problema subjacente. Sendo incapaz de serializar o lambda não tem nada a ver com o lambda ter "levantado" a sua variável. Se a expressão lambda precisa de uma instância de uma classe não-serialize para computação, faz todo o sentido que não pode ser serializado.

Dependendo do que você realmente está tentando fazer (eu não consigo decidir do seu post), uma solução seria mover a parte não-serializável do exterior lambda.

Por exemplo, em vez de:

NonSerializable nonSerializable = new NonSerializable();
Func<string, string> prependAction = (x => nonSerializable.ToString() + x);

uso:

NonSerializable nonSerializable = new NonSerializable();
string prefix = nonSerializable.ToString();
Func<string, string> prependAction = (x => prefix + x);

O que sobre este

string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Como sobre: ??

string prefix = "OLD:";
string prefixCopy = prefix;
Func<string, string> prependAction = (x => prefixCopy + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

?

Bem, se nós vamos falar sobre "problemas" aqui, lambdas vêm do mundo de programação funcional, e em um langauge programação puramente funcional, não existem atribuições e para que o seu problema nunca faria surgem porque o valor do prefixo nunca poderia mudar. Eu entendo C # acha que é legal para ideias de importação de programas funcionais (porque FP é legal!), Mas é muito difícil fazê-lo muito, porque C # é e será sempre uma linguagem de programação imperativa.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top