lambda 表达式中的事件 - C# 编译器错误?

发布于 2024-07-14 01:31:51 字数 1964 浏览 6 评论 0 原文

我正在考虑使用lambda表达式来允许事件以强类型的方式连接,但中间有一个侦听器,例如给定以下类

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:表达式树可能不包含赋值运算符

现在乍一看这似乎是合理的,特别是在 阅读有关为什么表达式树不能包含赋值的解释。 然而,尽管是 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”:无法显式调用运算符或访问器

因此,我想问题归结为 += 在 C# 事件上下文中的实际含义。 它意味着“调用此事件的添加方法”还是意味着“以尚未定义的方式添加到此事件”。 如果是前者,那么在我看来这是一个编译器错误,而如果是后者,那么它有点不直观,但可以说不是一个错误。 想法?

I was looking at using a lamba expression to allow events to be wired up in a strongly typed manner, but with a listener in the middle, e.g. given the following classes

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) { /* ... */ }
}

An event would be wired up as:

Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler);

However this gives a compiler error:

CS0832: An expression tree may not contain an assignment operator

Now at first this seems reasonable, particularly after reading the explanation about why expression trees cannot contain assignments. However, in spite of the C# syntax, the += is not an assignment, it is a call to the Producer::add_MyEvent method, as we can see from the CIL that is produced if we just wire the event up normally:

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)

So it looks to me like this is a compiler bug as it's complaining about assignments not being allowed, but there is no assignment taking place, just a method call. Or am I missing something...?

Edit:

Please note that the question is "Is this behaviour a compiler bug?". Sorry if I wasn't clear about what I was asking.

Edit 2

After reading Inferis' answer, where he says "at that point the += is considered to be assignment" this does make some sense, because at this point the compiler arguably doesn't know that it's going to be turned into CIL.

However I am not permitted to write the explicit method call form:

Listener.WireUp<Producer, Consumer>(
    (p, c) => p.add_MyEvent(new EventHandler(c.MyHandler)));

Gives:

CS0571: 'Producer.MyEvent.add': cannot explicitly call operator or accessor

So, I guess the question comes down to what += actually means in the context of C# events. Does it mean "call the add method for this event" or does it mean "add to this event in an as-yet undefined manner". If it's the former then this appears to me to be a compiler bug, whereas if it's the latter then it's somewhat unintuitive but arguably not a bug. Thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

情话已封尘 2024-07-21 01:31:51

在规范第 7.16.3 节中,+= 和 -= 运算符被称为“事件赋值”,这无疑使其听起来像赋值运算符。 事实上,它位于第 7.16 节(“赋值运算符”)内,这是一个相当大的提示:)从这个角度来看,编译器错误是有道理的。

不过,我同意它的限制过于严格,因为表达式树完全有可能表示 lambda 表达式给出的功能。

怀疑语言设计者采用了“限制性更强但操作符描述更一致”的方法,恐怕会以牺牲此类情况为代价。

In the spec, section 7.16.3, the += and -= operators are called "Event assignment" which certainly makes it sound like an assignment operator. The very fact that it's within section 7.16 ("Assignment operators") is a pretty big hint :) From that point of view, the compiler error makes sense.

However, I agree that it is overly restrictive as it's perfectly possible for an expression tree to represent the functionality given by the lambda expression.

I suspect the language designers went for the "slightly more restrictive but more consistent in operator description" approach, at the expense of situations like this, I'm afraid.

分分钟 2024-07-21 01:31:51

+= 是一个赋值,无论它做什么(例如添加一个事件)。 从解析器的角度来看,它仍然是一个赋值。

你试过了吗

Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; } );

+= is an assignment, no matter what it does (e.g. add an event). From the parser point of view, it is still an assignment.

Did you try

Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; } );
猥琐帝 2024-07-21 01:31:51

实际上,就编译器而言,它是一个赋值。
+= 运算符已重载,但编译器此时并不关心这一点。 毕竟,您是通过 lambda 生成一个表达式(该表达式在某一时刻将被编译为实际代码),而不是真正的代码。

所以编译器所做的是:创建一个表达式,将 c.MyHandler 添加到 p.MyEvent 的当前值,并将更改后的值存储回 p.MyEvent。 所以你实际上是在做一项作业,即使最终你没有做。

您是否希望 WireUp 方法采用表达式而不仅仅是操作?

Actually, as far as the compiler is concerned at that point, it is an assignment.
The += operator is overloaded, but the compiler doesn't care about that at it's point. After all, you're generating an expression through the lambda (which, at one point will be compiled to actual code) and no real code.

So what the compiler does is say: create an expression in where you add c.MyHandler to the current value of p.MyEvent and store the changed value back into p.MyEvent. And so you're actually doing an assignment, even if in the end you aren't.

Is there a reason you want the WireUp method to take an expression and not just an Action?

没有你我更好 2024-07-21 01:31:51

为什么要使用Expression类? 将代码中的 Expression> 更改为简单的 Action 并且所有内容都应该按您的意愿工作。 您在这里所做的就是强制编译器将 lambda 表达式视为表达式树而不是委托,并且表达式树确实不能包含此类赋值(它被视为赋值,因为我相信您正在使用 += 运算符)。 现在,lambda 表达式可以转换为任一形式(如 [MSDN][1] 中所述)。 通过简单地使用委托(这就是 Action 类的全部),这样的“分配”是完全有效的。 我可能误解了这里的问题(也许有一个特定的原因需要使用表达式树?),但幸运的是,解决方案似乎确实如此简单!

编辑:是的,我现在从评论中更好地理解了你的问题。 有什么理由不能只将 p.MyEvent 和 c.MyHandler 作为参数传递给 WireUp 方法并在 WireUp 方法中附加事件处理程序(对我来说,从设计的角度来看这似乎也更好)...会这不是消除了对表达式树的需要吗? 我认为无论如何,最好避免使用表达式树,因为与委托相比,它们往往相当慢。

Why do you want to use the Expression class? Change Expression<Action<TProducer, TConsumer>> in your code to simply Action<TProducer, TConsumer> and all should work as you want. What you're doing here is forcing the compiler to treat the lambda expression as an expression tree rather than a delegate, and an expression tree indeed cannot contain such assignments (it's treated as an assignment because you're using the += operator I believe). Now, a lambda expression can be converted into either form (as stated on [MSDN][1]). By simply using a delegate (that's all the Action class is), such "assignments" are perfectly valid. I may have misunderstood the problem here (perhaps there is a specific reason why you need to use an expression tree?), but it does seem like the solution is fortunately this simple!

Edit: Right, I understand your problem a bit better now from the comment. Is there any reason you can't just pass p.MyEvent and c.MyHandler as arguments to the WireUp method and attach the event handler within the WireUp method (to me this also seems better from a design point of view)... would that not eliminate the need for an expression tree? I think it's best if you avoid expression trees anyway, as they tend to be rather slow compared to delegates.

━╋う一瞬間旳綻放 2024-07-21 01:31:51

我认为问题是,除了 Expression 对象之外,从编译器的角度来看,表达式树不是静态类型的。 MethodCallExpression 和朋友不会公开静态类型信息。

尽管编译器知道表达式中的所有类型,但在将 lambda 表达式转换为表达式树时,此信息会被丢弃。 (看看为表达式树生成的代码)

尽管如此,我还是会考虑将其提交给微软。

I think the problem is, that apart from the Expression<TDelegate> object, the expression tree is not statically typed from the perspective of the compiler. MethodCallExpression and friends do not expose static typing information.

Even though the compiler knows all the types in the expression, this information is thrown away when converting the lambda expression to an expression tree. (Have a look at the code generate for expression trees)

I would nonetheless consider submitting this to microsoft.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文