5

前言:我试图在这里非常准确地描述这个场景。版本是“TL;DR我如何判断 lambda 将被编译为实例方法还是闭包”......

我在我的 WPF 项目中使用 MvvmLight,并且该库最近更改为使用WeakReference实例来保存传递到RelayCommand. 所以,实际上,我们在某处有一个对象,它持有 aWeakReference到 a Action<T>

现在,自从升级到最新版本后,我们的一些命令停止了工作。我们有一些这样的代码:

ctor(Guid token)
{
    Command = new RelayCommand(x => Messenger.Default.Send(x, token));
}

这导致生成一个关闭(如果我没有使用正确的术语,请纠正我)类 - 像这样:

[CompilerGenerated]
private sealed class <>c__DisplayClass4
{
    public object token;

    public void <.ctor>b__0(ReportType x)
    {
        Messenger.Default.Send<ReportTypeSelected>(new ReportTypeSelected(X), this.token); 
    }
}

这在以前工作得很好,因为操作存储在RelayCommand实例中,并且无论它被编译为实例方法还是闭包(即使用'<>DisplayClass'语法)都保持活动状态。

但是,现在,因为它保存在 a 中WeakReference,所以代码仅在指定的 lambda 编译为实例方法时才有效。这是因为闭包类被实例化,传递到RelayCommand几乎立即进行垃圾收集,这意味着当命令开始使用时,没有要执行的操作。因此,必须修改上面的代码。将其更改为以下原因,例如:

Guid _token;
ctor(Guid token)
{
    _token = token;
    Command = new RelayCommand(x => Messenger.Default.Send(x, _token));
}

这会导致编译后的代码产生一个成员 - 如下所示:

[CompilerGenerated]
private void <.ctor>b__0(ReportType x)
{
    Messenger.Default.Send<ReportTypeSelected>(new ReportTypeSelected(X), this._token);
}

现在上述一切都很好,我明白为什么它以前不起作用,以及改变它是如何导致它起作用的。然而,我剩下的东西意味着我现在编写的代码必须根据我不知情的编译器决定在风格上有所不同。

所以,我的问题是- 这是在所有情况下都有记录的行为 - 还是行为会根据编译器的未来实现而改变?我是否应该忘记尝试使用 lambda 并始终将实例方法传递给RelayCommand? 或者我应该有一个约定,即动作总是被缓存到一个实例成员中:

Action<ReportTypeSelected> _commandAction;
ctor(Guid token)
{
    _commandAction = x => Messenger.Default.Send(x, token);
    Command = new RelayCommand(_commandAction);
}

任何背景阅读指针也很感激地接受!

4

2 回答 2

3

无论您最终会在当前类上使用新类还是实例方法,都是您不应该依赖的实现细节

从 C# 规范,第 7.15.2 章(强调我的):

除了通过评估和调用 lambda 表达式或匿名方法表达式之外,是否有任何方法可以执行匿名函数块,这是明确未指定的。特别是,编译器可以选择通过合成一个或多个命名方法或类型来实现匿名函数。

-> 甚至没有指定它生成任何方法的事实。

鉴于这种情况,我会使用命名方法而不是匿名方法。如果这不可能,因为您需要从注册命令的方法中访问变量,您应该使用最后显示的代码。


RelayCommand在我看来,改变使用的决定WeakReference是一个糟糕的决定。它创造的问题比它解决的问题多得多。

于 2012-12-10T12:20:28.363 回答
2

一旦 lambda 引用任何自由变量(又名捕获),就会发生这种情况,因为它需要一个公共位置(又名存储类/闭包)来引用(和/或分配给)它们。

读者的一个练习是确定为什么这些存储类不能只是静态的。

于 2012-12-10T12:22:42.957 回答