C#Lambdas:如何*不*推迟“取消引用”?

时间:2010-02-15 20:05:33

标签: c# delegates lambda

我正在尝试使用C#委托实现撤消功能。基本上,我有一个UndoStack,它维护一个实现每个撤消操作的委托列表。当用户选择编辑:撤消时,此堆栈会弹出第一个代理并运行它。每个操作都负责将适当的撤消委托推送到堆栈。假设我有一个名为“SetCashOnHand”的方法。它看起来像这样:

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

因此,此方法构造撤消委托,执行操作,并(假设成功)将撤消委托推送到UndoStack。 (UndoStack足够聪明,如果在撤消的上下文中调用undoStack.Push,则代理会转到重做堆栈。)

我的麻烦在于,将this.cashOnHand“缓存”到coh变量中有点烦人。我希望我能写下这个:

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

但当然不会得到cashOnHand的现在值;它会在调用委托之前推迟查找值,因此代码最终无所事事。有没有什么办法可以在构造委托时“取消引用”cashOnHand,除了将值填充到像coh这样的局部变量中?

我对听到更好的撤销方法并不感兴趣。请将此视为关于代理人的一般性问题,使用撤消只是用于使问题更清晰的示例。

5 个答案:

答案 0 :(得分:6)

一般来说,除了你已经在做的事情之外,没有完美的解决方案。

但是,在您的具体情况下,您可以制作currier,如下所示:

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

你可以像这样使用它:

Curry(setCashOnHand, this.cashOnHand);

如果您的方法采用其他参数,您可以像这样使用它:

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

答案 1 :(得分:2)

不,如果您希望在特定时间进行评估,则必须在lambda之外捕获实例变量的值。

与你写这篇文章没什么不同:

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

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

lambda语法只是让它略显混乱,因为评估出现是声明方法体的一部分,而实际上它是自动生成的私有函数的一部分,它被评估为像任何一样其他功能。

人们通常会解决这个问题的方法是使用参数化函数并存储作为堆栈一部分传递给函数的值。但是,如果您想使用参数 less 函数,则必须在本地捕获它。

答案 2 :(得分:0)

没有任何其他神奇的方法可以做你想做的事。执行lambda时评估引用,而不是之前。当创建lambda而不是你正在做的事情时,没有一种自动的方法来“挂钩”被关闭的值。

答案 3 :(得分:0)

委托正在运行。我认为临时变量可能是处理“现在运行代码而不是将来运行代码”的最佳方式。但是,我猜你可以编写一个Bind()函数,将当前值绑定到委托......

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

答案 4 :(得分:0)

好吧,如果您的Undo堆栈仅由将撤消其自己的操作的实例引用,则无关紧要。

例如:

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

那么UndoStack引用的内容并不重要。如果您正在注入UndoStack,并且对此实例的引用会保存在其他位置,那么它就很重要了。

如果第二个是真的,我会看到重构这个而不是试图想出某种弱引用或其他诡计来防止实体通过撤销堆栈泄露。

我认为在你的情况下,每个级别都必须有自己的Undo堆栈。例如,UI层必须具有撤销堆栈,该堆栈将弹出下一个需要撤消的实例。或者,甚至,需要撤消的实例组,作为一个用户点击可能需要多个不同类型的实例按顺序执行撤消过程。