Domanda

Ho il seguente codice:

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

Perché il compilatore sostituisce la variabile prefisso con una chiusura " NOVITÀ: brownie " viene stampato sulla console.

Esiste un modo semplice per impedire al compilatore di sollevare la variabile prefisso mentre fa ancora uso di un'espressione lambda? Vorrei un modo per far funzionare il mio Func in modo identico a:

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

Il motivo per cui ho bisogno di questo è che vorrei serializzare il delegato risultante. Se la variabile prefisso è in una classe non serializzabile, la funzione precedente non verrà serializzata.

L'unico modo per evitarlo al momento è creare una nuova classe serializzabile che memorizza la stringa come variabile membro e ha il metodo prepend stringa:

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

Con classe helper:

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

Sembra molto lavoro extra per rendere il compilatore "stupido".

È stato utile?

Soluzione

Ora vedo il problema sottostante. È più profondo di quanto pensassi. Fondamentalmente la soluzione è modificare l'albero delle espressioni prima di serializzarlo, sostituendo tutti i sottotitoli che non dipendono dai parametri con nodi costanti. Questo è apparentemente chiamato "funzionletization". C'è una spiegazione qui .

Altri suggerimenti

Basta fare un'altra chiusura ...

Dire qualcosa di simile:

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"));

Non l'ho ancora testato perché al momento non ho accesso a VS, ma normalmente è così che risolvo questo problema.

Lambdas "succhia" automaticamente le variabili locali, temo che funzioni semplicemente per definizione.

Questo è un problema piuttosto comune, ovvero la modifica involontaria delle variabili da una chiusura: una soluzione molto più semplice è solo andare:

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

Se stai usando il resharper, in realtà identificherà i luoghi nel tuo codice in cui sei a rischio di causare effetti collaterali imprevisti come questo - quindi se il file è "tutto verde" il tuo codice dovrebbe essere OK.

Penso che per certi versi sarebbe stato carino se avessimo dello zucchero sintattico per gestire questa situazione, così avremmo potuto scriverlo come una riga, cioè

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

Laddove un operatore prefisso causerebbe la valutazione del valore della variabile prima della costruzione del delegato / funzione anonimo.

Ora capisco il problema: lambda si riferisce alla classe contenente che potrebbe non essere serializzabile. Quindi fai qualcosa del genere:

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

(Nota la parola chiave statica.) Quindi la lambda non deve fare riferimento alla classe contenente.

Ci sono già diverse risposte qui che spiegano come evitare il "sollevamento" lambda " la tua variabile. Purtroppo questo non risolve il problema di fondo. Non essere in grado di serializzare la lambda non ha nulla a che fare con la lambda che ha "alzato". la tua variabile. Se l'espressione lambda necessita di un'istanza di una classe non serializzata per il calcolo, ha perfettamente senso che non possa essere serializzata.

A seconda di ciò che stai effettivamente cercando di fare (non riesco proprio a decidere dal tuo post), una soluzione sarebbe quella di spostare all'esterno la parte non serializzabile della lambda.

Ad esempio, anziché:

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

utilizzo:

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

Che dire di questo

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

Che ne dici di:

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

Bene, se parleremo di " problemi " qui, i lambda provengono dal mondo della programmazione funzionale, e in un linguaggio di programmazione puramente funzionale, non ci sono compiti e quindi il tuo problema non sorgerebbe mai perché il valore del prefisso non potrebbe mai cambiare. Capisco che C # pensi che sia bello importare idee da programmi funzionali (perché FP è bello!) Ma è molto difficile renderlo carino, perché C # è e sarà sempre un linguaggio di programmazione imperativo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top