Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Lambdas: How *Not* to Defer "Dereference"?

I'm trying to implement undo functionality with C# delegates. Basically, I've got an UndoStack that maintains a list of delegates implementing each undo operation. When the user chooses Edit:Undo, this stack pops off the first delegate and runs it. Each operation is responsible for pushing an appropriate undo delegate unto the stack. So suppose I have a method called "SetCashOnHand." It looks like this:

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

So this method constructs an undo delegate, does the operation, and (assuming it succeeds) pushes the undo delegate onto the UndoStack. (UndoStack is smart enough that if undoStack.Push is called in the context of an undo, the delegate goes on the redo stack instead.)

My trouble is that it's a little annoying to "cache" this.cashOnHand into the coh variable. I wish I could just write this:

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

But of course that won't get the present value of cashOnHand; it defers looking up the value until the delegate is called, so the code winds up doing nothing. Is there any way I can "dereference" cashOnHand when constructing the delegate, other than stuffing the value into a local variable like coh?

I'm not really interested in hearing about better ways to do Undo. Please think of this as a generic question about delegates, with Undo just the example to make the question more clear.

like image 925
Paul A Jungwirth Avatar asked Feb 15 '10 20:02

Paul A Jungwirth


2 Answers

In general, there is no perfect solution to this other than what you're already doing.

However, in your specific case, you can make a currier, like this:

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

You can use it like this:

Curry(setCashOnHand, this.cashOnHand);

If your method takes other parameters, you can use it like this:

Curry(cashOnHand => setCashOnHand(3, cashOnHand), this.cashOnHand);
like image 160
SLaks Avatar answered Sep 19 '22 13:09

SLaks


No, you'll have to capture the value of your instance variable outside of the lambda if you want it evaluated at that particular time.

It's no different than if you'd written this:

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

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

The lambda syntax just make it slightly confusing since the evaluation appears to be a part of the declaring method body, when in reality it's part of an automatically-generated private function that gets evaluated just like any other function.

The way people generally get around this is by using a parameterized function and storing the value that gets passed to the function as part of the stack. If, however, you want to go with a parameter-less function, you'll have to capture it in a local.

like image 22
Adam Robinson Avatar answered Sep 22 '22 13:09

Adam Robinson