web-dev-qa-db-ja.com

C#イベントは舞台裏でどのように機能しますか?

私はC#、. NET3.5を使用しています。イベントの利用方法、クラスでの宣言方法、他の場所からのイベントのフック方法などを理解しています。不自然な例:

public class MyList
{
    private List<string> m_Strings = new List<string>();
    public EventHandler<EventArgs> ElementAddedEvent;

    public void Add(string value)
    {
        m_Strings.Add(value);
        if (ElementAddedEvent != null)
            ElementAddedEvent(value, EventArgs.Empty);
    }
}

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        MyList tmp = new MyList();
        tmp.ElementAddedEvent += new EventHandler<EventArgs>(Fired);
        tmp.Add("test");
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

しかし、私がしていることnot理解しているのは、イベントハンドラーを宣言するときです

public EventHandler<EventArgs> ElementAddedEvent;

初期化されることはありません-では、正確には、ElementAddedEventとは何ですか?それは何を指しているのですか? EventHandlerが初期化されないため、以下は機能しません。

[TestClass]
public class TestMyList
{
    private bool m_Fired = false;

    [TestMethod]
    public void TestEvents()
    {
        EventHandler<EventArgs> somethingHappend;
        somethingHappend += new EventHandler<EventArgs>(Fired);
        somethingHappend(this, EventArgs.Empty);
        Assert.IsTrue(m_Fired);
    }

    private void Fired(object sender, EventArgs args)
    {
        m_Fired = true;
    }
}

EventHandler.CreateDelegate(...)があることに気付きましたが、すべてのメソッドシグネチャは、これが通常のElementAddedEvent + = new EventHandler(MyMethod)を介して既存のEventHandlerにデリゲートをアタッチするためにのみ使用されることを示唆しています。

what私がやろうとしていることが役立つかどうかはわかりません...しかし、最終的には、LINQで抽象親DataContextを考え出し、その子が必要なテーブルタイプを登録できるようにします。監視された」ので、BeforeUpdateやAfterUpdateなどのイベントを設定できますが、タイプに固有です。このようなもの:

public class BaseDataContext : DataContext
{
    private static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> m_ObservedTypes = new Dictionary<Type, Dictionary<ChangeAction, EventHandler>>();

    public static void Observe(Type type)
    {
        if (m_ObservedTypes.ContainsKey(type) == false)
        {
            m_ObservedTypes.Add(type, new Dictionary<ChangeAction, EventHandler>());

            EventHandler eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Insert, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Update, eventHandler);

            eventHandler = EventHandler.CreateDelegate(typeof(EventHandler), null, null) as EventHandler;
            m_ObservedTypes[type].Add(ChangeAction.Delete, eventHandler);
        }
    }

    public static Dictionary<Type, Dictionary<ChangeAction, EventHandler>> Events
    {
        get { return m_ObservedTypes; }
    }
}


public class MyClass
{
    public MyClass()
    {
        BaseDataContext.Events[typeof(User)][ChangeAction.Update] += new EventHandler(OnUserUpdate);
    }

    public void OnUserUpdated(object sender, EventArgs args)
    {
        // do something
    }
}

これについて考えると、私はイベントの群れの下で何が起こっているのか本当に理解していないことに気づきました-そして私は理解したいと思います:)

41
Matt

私はこれを 記事 にかなり詳細に書いていますが、 代理人 自体にかなり満足していると仮定して、ここに要約を示します。

  • プロパティが実際には単なる「get」メソッドと「set」メソッドであるのと同じように、イベントは単なる「add」メソッドと「remove」メソッドです。 (実際、CLIでは「発生/起動」メソッドも許可されていますが、C#ではこれが生成されません。)メタデータは、メソッドへの参照を使用してイベントを記述します。
  • フィールドのようなイベント(ElementAddedEventのように)を宣言すると、コンパイラはメソッドおよびプライベートフィールド(デリゲートと同じタイプ)。クラス内で、ElementAddedEventを参照するときは、フィールドを参照しています。クラスの外では、フィールドを参照しています。
  • Addメソッドを呼び出すイベント(+ =演算子を使用)を誰かがサブスクライブしたとき。それらが(-=演算子を使用して)サブスクライブを解除すると、removeが呼び出されます。
  • フィールドのようなイベントの場合、同期がありますが、それ以外の場合、追加/削除はDelegateを呼び出すだけです。 Combine / Remove 自動生成されたフィールドの値を変更します。これらの操作は両方ともバッキングフィールドに割り当てられます-デリゲートは不変であることを忘れないでください。言い換えると、自動生成されたコードは次のようになります。

    // Backing field
    // The underscores just make it simpler to see what's going on here.
    // In the rest of your source code for this class, if you refer to
    // ElementAddedEvent, you're really referring to this field.
    private EventHandler<EventArgs> __ElementAddedEvent;
    
    // Actual event
    public EventHandler<EventArgs> ElementAddedEvent
    {
        add
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent += value;
                __ElementAddedEvent = Delegate.Combine(__ElementAddedEvent, value);
            }
        }
        remove
        {
            lock(this)
            {
                // Equivalent to __ElementAddedEvent -= value;
                __ElementAddedEvent = Delegate.Remove(__ElementAddedEvent, value);
            }
        }
    }
    
  • この場合に生成されるフィールドの初期値はnull-であり、すべてのサブスクライバーが削除されると、Delegate.Removeの動作であるため、常にnullになります。

  • ヌルチェックを回避するために、「no-op」ハンドラーでイベントをサブスクライブする場合は、次のようにします。

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

    delegate {}は、パラメータを気にせず、何もしない匿名メソッドです。

それでも不明な点がある場合は、お問い合わせください。サポートさせていただきます。

69
Jon Skeet