前言:我试图在这里非常准确地描述这个场景。 TL;DR
版本是'如何判断lambda是否会被编译成实例方法或闭包'...
我在我的WPF项目中使用MvvmLight,并且该库最近更改为使用WeakReference
实例以保存传递到RelayCommand
的操作。所以,实际上,我们在某个地方有一个WeakReference
到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
实例中,并且无论是编译为实例方法还是闭包(即使用'&lt;&gt; DisplayClass'语法),它都保持活动状态
但是,现在,因为它保存在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);
}
现在上面的一切都很好,我理解为什么它以前不起作用,以及如何改变它导致它工作。但是,我留下的东西意味着我现在编写的代码必须在风格上有所不同,这取决于我不知情的编译器决策。
所以,我的问题是 - 这在所有情况下都是记录在案的行为 - 或者行为会根据编译器的未来实现而改变吗?我是否应该忘记尝试使用lambdas并始终将实例方法传递给RelayCommand
?或者我是否应该有一个约定,其中操作始终缓存到实例成员中:
Action<ReportTypeSelected> _commandAction;
ctor(Guid token)
{
_commandAction = x => Messenger.Default.Send(x, token);
Command = new RelayCommand(_commandAction);
}
任何背景阅读指针也都很感激!
答案 0 :(得分:3)
您是否最终会在当前类上使用新类或实例方法是不依赖的实现细节。
从C#规范,第7.15.2章(强调我的):
除了通过评估和调用lambda-expression或anonymous-method-expression之外,是否有任何方法可以执行匿名函数的块,这是明确未指定的。特别是,编译器可能选择通过合成一个或多个命名方法或类型来实现匿名函数。
- &GT;甚至没有指定它根本生成任何方法的事实。
鉴于这种情况,我会使用命名方法而不是匿名方法。如果那是不可能的,因为你需要从注册命令的方法中访问变量,你应该使用你最后显示的代码。
在我看来,将RelayCommand
更改为使用WeakReference
的决定很糟糕。它创造了比解决的问题更多的问题。
答案 1 :(得分:2)
一旦lambda引用任何自由变量(也就是捕获),那么这将发生,因为它需要一个公共位置(也称为存储类/闭包)来引用(和/或分配)它们。
读者的练习是确定为什么这些存储类不能只是静态的。