问题
I was writing some tests for one of my classes and I needed to test that an event was being raised. Out of just trying it and seeing what happened I coded something similar to the following extremely simplified code.
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);
}
}
I wasn't so much amazed when it worked as when I started to try and figure out how it worked. Specifically, how would I write this without lambda expressions so that the local variable raised
can be updated? In other words, how is the compiler refactoring/translating this?
I got this far...
[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
}
}
But this changes raised
to a class-scoped field rather than a local-scope variable.
回答1:
Specifically, how would I write this without lambda expressions so that the local variable raised can be updated?
You would create an extra class, to hold the captured variables. That's what the C# compiler does. The extra class would contain a method with the body of the lambda expression, and the EventRaised
method would create an instance of that capturing class, using the variables within that instance instead of "real" local variables.
It's easiest to demonstrate this without using events - just a small console application. Here's the version with the lambda expression:
using System;
class Test
{
static void Main()
{
int x = 10;
Action increment = () => x++;
increment();
increment();
Console.WriteLine(x); // 12
}
}
And here's code which is similar to the code generated by the compiler:
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
}
}
Of course it can get much more complicated than this, particularly if you have multiple captured variables with different scopes - but if you can understand how the above works, that's a big first step.
回答2:
Compiler generates class like this, which has method with signature of lambda delegate. All captured local variables moved to this class fields:
public sealed class c_0
{
public bool raised;
public void m_1(object s, EventArgs e)
{
// lambda body goes here
raised = true;
}
}
And final compiler trick is replacing usages of local raised
variable with this field of generated class:
[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);
}
}
来源:https://stackoverflow.com/questions/13635420/how-can-lambda-expressions-as-event-handlers-can-change-local-variables