2010-02-15 7 views
9

Estoy tratando de implementar la funcionalidad deshacer con C# delegados. Básicamente, tengo un UndoStack que mantiene una lista de delegados implementando cada operación de deshacer. Cuando el usuario elige Editar: Deshacer, esta pila sale del primer delegado y lo ejecuta. Cada operación es responsable de empujar un delegado de deshacer apropiado a la pila. Entonces supongamos que tengo un método llamado "SetCashOnHand". Se ve así:C# Lambdas: ¿Cómo * No * para aplazar la "desreferencia"?

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

Así que este método construye un delegado de deshacer, que hace la operación, y (suponiendo que tenga éxito) empuja el delegado de deshacer en el UndoStack. (UndoStack es lo suficientemente inteligente como para llamar a undoStack.Push en el contexto de una operación de deshacer, el delegado va a la pila de rehacer).

Mi problema es que es un poco molesto "cachear" esta.cashOnHand en el variable coh. Ojalá pudiera escribir esto:

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

Pero por supuesto que no va a obtener el valor actual de cashOnHand; difiere buscando el valor hasta que se llame al delegado, por lo que el código termina sin hacer nada. ¿Hay alguna forma de que pueda "eliminar la referencia" de CashOnHand cuando construya el delegado, aparte de rellenar el valor en una variable local como coh?

No estoy realmente interesado en conocer mejores formas de deshacer. Por favor piense en esto como una pregunta genérica sobre delegados, con Deshacer solo el ejemplo para hacer la pregunta más clara.

+1

Supongo que te refieres a _Edit_, Deshacer. – SLaks

+0

Vaya, tienes razón. He arreglado la pregunta. –

Respuesta

6

En general, no existe una solución perfecta para esto aparte de lo que ya está haciendo.

Sin embargo, en su caso específico, se puede hacer una currier, así:

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

Usted puede utilizar de esta manera:

Curry(setCashOnHand, this.cashOnHand); 

Si su método tiene otros parámetros, se puede úselo de esta manera:

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

Esta parece ser la mejor alternativa. Pero es tan confuso, creo que me limitaré a copiar el valor a una variable local. –

+0

Pureza vs Mantenimiento. =) –

2

No, tendrá que capturar el valor de su variable de instancia fuera de la lambda si quiere que se evalúe en ese momento en particular.

No es diferente que si hubieras escrito esto:

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

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

La sintaxis lambda solo lo hacen un poco confuso ya que la evaluación aparece a ser una parte del cuerpo del método de declarar, cuando en realidad es parte de una función privada generada automáticamente que se evalúa como cualquier otra función.

La forma en que las personas generalmente solucionan esto es mediante el uso de una función parametrizada y almacenando el valor que pasa a la función como parte de la pila. Sin embargo, si desea ir con un parámetro menos función, tendrá que capturarlo en un local.

+1

No creo que esto funcione ... cuando se llame a UndoSetCashOnHand, obtendrá el valor de cashOnHand en el tiempo de deshacer, no en el momento de SetCOH. – Jimmy

+1

@Jimmy: Ese es exactamente el punto. Esto es lo que hace su lambda *, y pregunta si hay alguna manera de que * no * haga eso. Lo que quiero decir es que no tendría sentido. El código no pretendía ser una solución alternativa, es una representación * alternativa de lo que realmente está haciendo. –

+0

Ya veo. perdone mi falta de comprensión. – Jimmy

0

No hay realmente otra forma mágica de hacer lo que le gustaría. La referencia se evalúa cuando se ejecuta la lambda, no antes. No hay una forma automática de "enganchar" el valor que se está cerrando cuando se creó el lambda que no sea lo que está haciendo.

0

El delegado se está ejecutando en el futuro .. I piense que la variable temp es probablemente la mejor manera de manejar "ejecutar código ahora, en lugar de hacerlo en el futuro". Sin embargo, supongo que se podría escribir una función bind() que se une un valor actual a un delegado ..

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

Vaya, Curry es un nombre mejor :) – Jimmy

0

Bueno, si su pila de deshacer se hace referencia únicamente por la instancia que va a ser la ruina de sus propias acciones, a continuación, No importa.

Por ejemplo:

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

entonces no importa lo que las referencias UndoStack. Si está inyectando una UndoStack y las referencias a esta instancia se llevan a cabo en otro lado, entonces sí que importarían.

Si el segundo es cierto, me gustaría volver a elaborar esto en lugar de intentar encontrar algún tipo de referencia débil u otra argucia para evitar que se filtren instancias a través de la pila de deshacer.

Supongo que en su caso cada nivel debería tener su propia pila de Deshacer. Por ejemplo, la capa de IU debería tener una pila de deshacer que se abrirá en la instancia que se debe deshacer a continuación. O, incluso, los grupos de instancias que deben deshacerse, ya que un clic del usuario puede requerir varias instancias de diferentes tipos, pasar por un procedimiento de deshacer en orden.

Cuestiones relacionadas