我正在为我的一个课程编写一些测试,我需要测试一个事件是否正在被提出。只是尝试它并看到发生了什么,我编写了类似于以下非常简化的代码。
public class MyEventClass
{
public event EventHandler MyEvent;
public void MethodThatRaisesMyEvent()
{
if (MyEvent != null)
MyEvent(this, new EventArgs());
}
}
[TestClass]
public class MyEventClassTest
{
[TestMethod]
public void EventRaised()
{
bool raised = false;
var subject = new MyEventClass();
subject.MyEvent += (s, e) => raised = true;
subject.MethodThatRaisesMyEvent();
Assert.IsTrue(raised);
}
}
当我开始尝试弄清楚它是如何工作的时候,我并没有那么惊讶。具体来说,如何在没有lambda表达式的情况下编写它,以便可以更新局部变量raised
?换句话说,编译器如何重构/翻译呢?
我到目前为止......
[TestClass]
public class MyEventClassTestRefactor
{
private bool raised;
[TestMethod]
public void EventRaised()
{
raised = false;
var subject = new MyEventClass();
subject.MyEvent += MyEventHandler;
subject.MethodThatRaisesMyEvent();
Assert.IsTrue(raised);
}
private void MyEventHandler(object sender, EventArgs e)
{
raised = true
}
}
但是这会将raised
更改为类范围的字段而不是局部范围的变量。
答案 0 :(得分:11)
具体来说,如何在没有lambda表达式的情况下编写它,以便可以更新引发的局部变量?
您将创建一个额外的类,以保存捕获的变量。这就是C#编译器的功能。额外的类将包含一个带有lambda表达式主体的方法,EventRaised
方法将创建该捕获类的实例,使用该实例中的变量而不是“real”局部变量。
最简单的方法就是在不使用事件的情况下展示它 - 只是一个小型控制台应用程序。这是lambda表达式的版本:
using System;
class Test
{
static void Main()
{
int x = 10;
Action increment = () => x++;
increment();
increment();
Console.WriteLine(x); // 12
}
}
这里的代码类似于编译器生成的代码:
using System;
class Test
{
private class CapturingClass
{
public int x;
public void Execute()
{
x++;
}
}
static void Main()
{
CapturingClass capture = new CapturingClass();
capture.x = 10;
Action increment = capture.Execute;
increment();
increment();
Console.WriteLine(capture.x); // 12
}
}
当然它可以让更多比这更复杂,特别是如果你有多个捕获的变量具有不同的范围 - 但如果你能理解上面的工作原理,那么这是一个很大的第一步。
答案 1 :(得分:1)
编译器生成这样的类,它具有lambda委托签名的方法。所有捕获的局部变量都移到了这个类字段:
public sealed class c_0
{
public bool raised;
public void m_1(object s, EventArgs e)
{
// lambda body goes here
raised = true;
}
}
最终的编译器技巧是用生成的类的这个字段替换本地raised
变量的用法:
[TestClass]
public class MyEventClassTest
{
[TestMethod]
public void EventRaised()
{
c_0 generated = new c_0();
generated.raised = false;
var subject = new MyEventClass();
subject.MyEvent += generated.m_1;
subject.MethodThatRaisesMyEvent();
Assert.IsTrue(generated.raised);
}
}