Mat*_*eld 5 c# lambda closures mvvm-light
前言:我试图在这里非常精确地描述这个场景.该TL;DR版本是"我怎么知道,如果一个lambda会被编译成一个实例方法或关闭" ...
我在我的WPF项目中使用MvvmLight,并且该库最近更改为使用WeakReference实例以保存传递给的动作RelayCommand.因此,有效的,我们有正拿着一个对象某处WeakReference到Action<T>.
现在,自升级到最新版本以来,我们的一些命令停止了工作.我们有一些像这样的代码:
ctor(Guid token)
{
Command = new RelayCommand(x => Messenger.Default.Send(x, token));
}
Run Code Online (Sandbox Code Playgroud)
这导致关闭(请纠正我,如果我没有使用正确的术语)类生成 - 像这样:
[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);
}
}
Run Code Online (Sandbox Code Playgroud)
之前工作正常,因为操作存储在RelayCommand实例中,并且无论是编译为实例方法还是闭包(即使用'<> DisplayClass'语法),它都保持活动状态.
但是,现在,因为它保存在a中WeakReference,所以只有将指定的lambda编译为实例方法时,代码才有效.这是因为闭包类被实例化,传递到RelayCommand并且几乎立即被垃圾收集,这意味着当命令被使用时,没有动作要执行.因此,必须修改上述代码.将其更改为以下原因,例如:
Guid _token;
ctor(Guid token)
{
_token = token;
Command = new RelayCommand(x => Messenger.Default.Send(x, _token));
}
Run Code Online (Sandbox Code Playgroud)
这会导致编译的代码导致成员 - 如下所示:
[CompilerGenerated]
private void <.ctor>b__0(ReportType x)
{
Messenger.Default.Send<ReportTypeSelected>(new ReportTypeSelected(X), this._token);
}
Run Code Online (Sandbox Code Playgroud)
现在上面的一切都很好,我理解为什么它以前不起作用,以及如何改变它导致它工作.但是,我留下的东西意味着我现在编写的代码必须在风格上有所不同,这取决于我不知情的编译器决策.
所以,我的问题是 - 在所有情况下这是一个记录在案的行为 - 或者行为是否会根据编译器的未来实现而改变?我应该忘记尝试使用lambdas并始终将实例方法传递给RelayCommand?或者我应该有一个约定,其中操作始终缓存到实例成员:
Action<ReportTypeSelected> _commandAction;
ctor(Guid token)
{
_commandAction = x => Messenger.Default.Send(x, token);
Command = new RelayCommand(_commandAction);
}
Run Code Online (Sandbox Code Playgroud)
任何背景阅读指针也非常感谢!
无论您最终会得到一个新类还是当前类的实例方法,都是您不应该依赖的实现细节。
来自 C# 规范第 7.15.2 章(强调我的):
除了通过求值和调用 lambda 表达式或匿名方法表达式之外,是否有任何方法可以执行匿名函数的块是明确未指定的。特别是,编译器可以选择通过合成一个或多个命名方法或类型来实现匿名函数。
-> 甚至没有指定它生成任何方法的事实。
鉴于这种情况,我会使用命名方法而不是匿名方法。如果这是不可能的,因为您需要从注册命令的方法访问变量,您应该使用最后显示的代码。
RelayCommand在我看来,改变用途的决定WeakReference是一个糟糕的决定。它产生的问题远多于它解决的问题。