2010-02-15 13 views
9

Sto tentando di implementare la funzionalità di annullamento con i delegati C#. Fondamentalmente, ho un UndoStack che mantiene un elenco di delegati che implementano ogni operazione di annullamento. Quando l'utente sceglie Modifica: Annulla, questo stack apre il primo delegato e lo esegue. Ogni operazione è responsabile di spingere un delegato di annullamento appropriato nello stack. Quindi supponiamo di avere un metodo chiamato "SetCashOnHand". Ecco come si presenta:C# Lambdas: Come * Non * rimandare "Dereferenziazione"?

public void SetCashOnHand(int x) { 
    int coh = this.cashOnHand; 
    undo u =() => this.setCashOnHand(coh); 
    this.cashOnHand = x; 
    undoStack.Push(u); 
} 

Quindi questo metodo costruisce un delegato annullamento, fa l'operazione, e (ammesso che riesce) spinge il delegato annullamento sul UndoStack. (UndoStack è abbastanza intelligente che se undoStack.Push viene chiamato nel contesto di un annullamento, il delegato va invece sulla pila di ripetizione.)

Il mio problema è che è un po 'fastidioso "cache" this.cashOnHand nel variabile coh. Vorrei poter scrivere semplicemente questo:

undo u =() => this.setCashOnHand(this.cashOnHand); 

Ma, naturalmente, che non sarà possibile ottenere l'attuale valore della cashOnHand ; rimanda a cercare il valore finché non viene chiamato il delegato, quindi il codice si conclude senza fare nulla. C'è un modo in cui posso "dereferenziare" cashOnHand quando costruisco il delegato, oltre a riempire il valore in una variabile locale come coh?

Non sono molto interessato a conoscere i modi migliori per annullare. Si prega di considerare questo come una domanda generica sui delegati, con Annulla solo l'esempio per rendere la domanda più chiara.

+1

Suppongo che tu intenda _Edit_, Annulla. – SLaks

+0

Oops, hai ragione. Ho risolto la domanda. –

risposta

6

In generale, non esiste una soluzione perfetta per questo diverso da quello che stai già facendo.

Tuttavia, nel caso specifico, è possibile effettuare una currier, come questo:

static Action Curry(Action<T> method, T param) { return() => method(param); } 

è possibile utilizzarlo in questo modo:

Curry(setCashOnHand, this.cashOnHand); 

Se il metodo prende altri parametri, è possibile usare in questo modo:

Curry(cashOnHand => setCashOnHand(3, cashOnHand), this.cashOnHand); 
+1

Questa sembra la migliore alternativa. Ma è così confuso, penso che continuerò a copiare il valore su una variabile locale. –

+0

Purezza vs manutenzione. =) –

2

No, è necessario acquisire il valore della variabile di istanza al di fuori del lambda se si desidera che venga valutato in quel particolare momento.

Non è diverso se avessi scritto questo:

public void SetCashOnHand(int x) { 
    this.cashOnHand = x; 
    undoStack.Push(UndoSetCashOnHand); 
} 

private void UndoSetCashOnHand() 
{ 
    this.setCashOnHand(this.cashOnHand); 
} 

La sintassi lambda solo renderlo un po 'di confusione in quanto la valutazione sembra essere una parte del corpo del metodo dichiarando, quando in realtà si tratta di parte di una funzione privata generata automaticamente che viene valutata come qualsiasi altra funzione.

Il modo in cui le persone si aggirano generalmente è utilizzando una funzione parametrizzata e memorizzando il valore che viene passato alla funzione come parte dello stack. Se, tuttavia, vuoi utilizzare una funzione in meno di, devi catturarla in locale.

+1

Non penso che questo funzioni .... quando viene chiamato UndoSetCashOnHand, otterrà il valore di cashOnHand a tempo di annullamento, non al momento SetCOH. – Jimmy

+1

@Jimmy: Questo è esattamente il punto. Questo è ciò che fa il suo lambda *, e sta chiedendo se c'è un modo per farlo * non * per farlo. Il punto che sto facendo è che non avrebbe senso. Il codice non doveva essere una soluzione alternativa, è una rappresentazione * alternativa * di ciò che sta effettivamente facendo. –

+0

Capisco. scusa per l'incomprensione. – Jimmy

0

Non c'è davvero nessun altro modo magico per fare ciò che desideri. Il riferimento viene valutato quando viene eseguita la lambda, non prima. Non c'è un modo automatico per "agganciare" il valore che viene chiuso quando il lambda è stato creato diverso da quello che stai facendo.

0

il delegato è in esecuzione in futuro .. ho penso che la variabile temp sia probabilmente il modo migliore per gestire "esegui il codice ora, invece che nel futuro". Tuttavia, penso che si potrebbe scrivere una funzione Bind() che lega un valore di corrente di un delegato ..

Action Bind<T>(Func<T> f, T value) { 
    return (Action)(() => f(value)); 
} 
undoStack.Push(Bind(this.setCashOnHand, this.cashOnHand)); 
+2

oops, Curry è un nome migliore :) – Jimmy

0

Beh, se il vostro stack di annullamento viene fatto riferimento solo l'istanza che verrà disfacendo le proprie azioni, allora non importa.

Per esempio:

public class MyType 
{ 

    private UndoStack undoStack {get;set;} 

    public void SetCashOnHand(int x) { 
    int coh = this.cashOnHand; 
    undo u =() => this.setCashOnHand(coh); 
    this.cashOnHand = x; 
    undoStack.Push(u); 
    } 
} 

allora non importa ciò che i riferimenti UndoStack. Se si sta iniettando un UndoStack e i riferimenti a questa istanza sono trattenuti altrove, sarebbe importante.

Se il secondo è vero, vedrei di refactoring questo piuttosto che tentare di venire con qualche tipo di riferimento debole o altri trucchi per evitare che le istanze sono trapelate tramite lo stack di annullamento.

Suppongo che nel tuo caso ogni livello dovrebbe avere il proprio stack di annullamento. Ad esempio, il livello dell'interfaccia utente dovrebbe disporre di uno stack di annullamento che compili l'istanza che deve essere annullata successivamente. Oppure, anche, i gruppi di istanze che devono essere annullate, poiché un clic dell'utente può richiedere più istanze di tipi diversi, passano attraverso una procedura di annullamento in ordine.

Problemi correlati