我正在考虑使用lamba表达式来允许以强类型方式连接事件,但是在中间使用侦听器,例如给出以下课程
class Producer
{
public event EventHandler MyEvent;
}
class Consumer
{
public void MyHandler(object sender, EventArgs e) { /* ... */ }
}
class Listener
{
public static void WireUp<TProducer, TConsumer>(
Expression<Action<TProducer, TConsumer>> expr) { /* ... */ }
}
事件将连接为:
Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler);
但是这会产生编译错误:
CS0832:表达式树可能不包含赋值运算符
现在起初这似乎是合理的,特别是在reading the explanation about why expression trees cannot contain assignments之后。但是,尽管有C#语法,+=
不是赋值,但它是对Producer::add_MyEvent
方法的调用,正如我们从CIL中看到的那样,如果我们只是将事件连接起来通常:
L_0001: newobj instance void LambdaEvents.Producer::.ctor()
L_0007: newobj instance void LambdaEvents.Consumer::.ctor()
L_000f: ldftn instance void LambdaEvents.Consumer::MyHandler(object, class [mscorlib]System.EventArgs)
L_0015: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_001a: callvirt instance void LambdaEvents.Producer::add_MyEvent(class [mscorlib]System.EventHandler)
因此,我认为这是一个编译器错误,因为它抱怨不允许分配,但没有分配发生,只是一个方法调用。或者我错过了什么......?
修改
请注意,问题是“此行为是编译器错误吗?”。对不起,如果我不清楚我在问什么。
修改2
在阅读了Inferis的回答之后,他说“当时+ =被认为是赋值”这确实有意义,因为此时编译器可能不知道它会变成CIL
但是我不允许编写显式方法调用表单:
Listener.WireUp<Producer, Consumer>(
(p, c) => p.add_MyEvent(new EventHandler(c.MyHandler)));
给出:
CS0571:'Producer.MyEvent.add':无法显式调用operator或accessor
所以,我想这个问题归结为+=
在C#事件的上下文中实际意味着什么。这是否意味着“为此事件调用add方法”或者是否意味着“以尚未定义的方式添加到此事件”。如果它是前者,那么在我看来这是一个编译器错误,而如果它是后者那么它有点不直观但可以说不是一个错误。想法?
答案 0 :(得分:5)
在规范7.16.3节中,+ =和 - =运算符被称为“事件赋值”,它肯定会使它听起来像赋值运算符。事实上,它在7.16节(“赋值运算符”)中是一个相当大的提示:)从这个角度来看,编译器错误是有道理的。
但是,我同意 过度限制,因为表达式树完全有可能表示lambda表达式给出的功能。
我怀疑语言设计师采用“稍微限制但操作员描述更加一致”的方法,以牺牲这样的情况为代价,我担心。
答案 1 :(得分:1)
+ =是一项任务,无论它做什么(例如添加一个事件)。从解析器的角度来看,它仍然是一项任务。
你试过吗
Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; } );
答案 2 :(得分:1)
为什么要使用Expression类?将代码中的Expression<Action<TProducer, TConsumer>>
更改为Action<TProducer, TConsumer>
,所有内容都可以按您的意愿运行。你在这里做的是迫使编译器将lambda表达式视为表达式树而不是委托,并且表达式树确实不能包含这样的赋值(它被视为赋值,因为你使用的是+ =运算符我相信)。现在,lambda表达式可以转换为任何一种形式(如[MSDN] [1]中所述)。通过简单地使用委托(这是所有的Action类),这样的“赋值”是完全有效的。我可能在这里误解了这个问题(可能有一个特定的原因,你需要使用表达式树?),但似乎解决方案似乎很幸运,这很简单!
编辑:是的,我现在从评论中了解您的问题。有没有什么理由你不能只将p.MyEvent和c.MyHandler作为参数传递给WireUp方法并在WireUp方法中附加事件处理程序(对我而言,从设计的角度看这似乎也更好)...会那不能消除对表达式树的需求吗?我认为最好还是避免使用表达树,因为与代表相比,它们往往相当慢。
答案 3 :(得分:1)
实际上,就编译器而言, 是一个赋值。 + =运算符被重载,但编译器并不关心它。毕竟,你正在通过lambda生成一个表达式(在某一点上将被编译为实际代码)并且没有真正的代码。
编译器的作用是:创建一个表达式,将c.MyHandler
添加到p.MyEvent
的当前值,并将更改后的值存储回p.MyEvent
。所以你实际上正在做一项任务,即使最终你不是。
您是否有理由希望WireUp方法采用表达式而不仅仅是Action?
答案 4 :(得分:0)
我认为问题是,除了Expression<TDelegate>
对象之外,表达式树不是从编译器的角度静态类型化的。 MethodCallExpression
和朋友不公开静态输入信息。
即使编译器知道表达式中的所有类型,在将lambda表达式转换为表达式树时也会抛弃此信息。 (看看为表达式树生成的代码)
尽管如此,我还是会考虑将其提交给微软。