我正在为我的一个类编写一些测试,我需要测试是否正在引发事件。 只是尝试一下,看看发生了什么,我编写了一些类似于以下极其简化的代码的东西。
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
更改为类范围的字段,而不是局部范围的变量。
具体来说,我将如何在没有 lambda 表达式的情况下编写它,以便可以更新引发的局部变量?
您将创建一个额外的类来保存捕获的变量。这就是 C# 编译器的作用。额外的类将包含一个带有 lambda 表达式主体的方法,EventRaised
方法将使用该实例中的变量而不是"真正的"局部变量创建该捕获类的实例。
最简单的方法是不使用事件来演示这一点 - 只是一个小型控制台应用程序。下面是带有 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
}
}
当然,它可能会变得比这复杂得多,特别是如果你有多个具有不同范围的捕获变量 - 但如果你能理解上述工作原理,那就是迈出重要的第一步。
生成这样的类,它具有带有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);
}
}