1

クラスの 1 つのテストを書いていて、イベントが発生していることをテストする必要がありました。試してみて、何が起こったのかを見てから、次の非常に単純化されたコードに似たものをコーディングしました。

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);
    }
}

それがどのように機能するかを理解し始めたときほど、私はそれが機能したときほど驚きませんでした. 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は、ローカル スコープ変数ではなく、クラス スコープ フィールドに変更されます。

4

2 に答える 2

11

具体的には、発生したローカル変数を更新できるように、ラムダ式を使用せずにこれをどのように記述すればよいでしょうか?

キャプチャされた変数を保持するために、追加のクラスを作成します。それが C# コンパイラの機能です。余分なクラスには、ラムダ式の本体を持つメソッドが含まれ、メソッドは、「実際の」ローカル変数の代わりにそのインスタンス内の変数を使用して、そのキャプチャ クラスのインスタンスEventRaisedを作成します。

イベントを使用せずにこれを示すのが最も簡単です。小さなコンソール アプリケーションだけです。ラムダ式を使用したバージョンは次のとおりです。

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
    }
}

もちろん、これよりもはるかに複雑になる可能性があります。特に、スコープが異なる複数のキャプチャされた変数がある場合はなおさらです。しかし、上記の仕組みを理解できれば、それは大きな第一歩です。

于 2012-11-29T21:54:59.893 に答える
1

コンパイラは、ラムダデリゲートのシグネチャを持つメソッドを持つこのようなクラスを生成します。キャプチャされたすべてのローカル変数は、このクラス フィールドに移動されました。

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);
    }
}
于 2012-11-29T21:58:44.647 に答える