41

C#、.NET 3.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;
    }
}

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

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 にデリゲートをアタッチするためにのみ使用されることを示唆しています。

私がやろうとしていることが役立つかどうかはわかりません...しかし、最終的には、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
    }
}

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

4

2 に答える 2

72

これについては記事でかなり詳しく説明しましたが、デリゲート自体にある程度満足していると仮定して、要約を以下に示します。

  • プロパティが実際には単なる「取得」メソッドと「設定」メソッドであるのと同じように、イベントは単なる「追加」メソッドと「削除」メソッドです。(実際、CLI では "raise/fire" メソッドも使用できますが、C# ではこれが生成されることはありません。) メタデータは、メソッドへの参照を使用してイベントを記述します。
  • フィールドのようなイベント(ElementAddedEvent など)を宣言すると、コンパイラはメソッドとプライベート フィールド(デリゲートと同じ型) を生成します。クラス内で ElementAddedEvent を参照するときは、フィールドを参照しています。クラスの外では、フィールドを参照しています。
  • add メソッドを呼び出すイベント (+= 演算子を使用) を誰かがサブスクライブしたとき。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- でありnull、すべてのサブスクライバーが削除されると、Delegate.Remove の動作であるため、常に再びなります。

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

    public EventHandler<EventArgs> ElementAddedEvent = delegate {};
    

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

まだ不明な点がある場合は、お問い合わせください。お手伝いします!

于 2008-10-17T20:05:03.570 に答える
-3

内部的には、イベントは特別な呼び出し規則を持つ単なるデリゲートです。(たとえば、イベントを発生させる前に nullity をチェックする必要はありません。)

擬似コードでは、Event.Invoke() は次のように分解されます。

イベントにリスナーがある場合 このスレッドで任意の順序で各リスナーを同期的に呼び出します。

イベントはマルチキャストであるため、0 個以上のリスナーがあり、コレクションに保持されます。CLR はそれらをループし、それぞれを任意の順序で呼び出します。

覚えておくべき重要な注意点の 1 つは、イベント ハンドラーは、イベントが発生したスレッドと同じスレッドで実行されるということです。イベント ハンドラーが新しいスレッドを生成していると考えるのは、一般的な精神的誤りです。彼らはしない。

于 2008-10-17T20:06:48.387 に答える