1. ホーム
  2. c#

+= new EventHandler(Method) vs += Method [重複].

2023-08-09 03:31:46

質問

重複の可能性があります。

C#: ' += anEvent' と ' += new EventHandler(anEvent)' の違い。

イベントを購読するには、2つの基本的な方法があります。

SomeEvent += new EventHandler<ArgType> (MyHandlerMethod);
SomeEvent += MyHandlerMethod;

どのような違いがあるのでしょうか、またどのような場合にどちらを選べばよいのでしょうか。

編集:もし同じなら、なぜVSは長いバージョンをデフォルトにして、コードを乱雑にしているのでしょうか?それは私にはまったく意味がありません。

どのように解決するのですか?

私の最初の回答に対して論争があるようなので、生成されたコードを見ることを含めて、いくつかのテストをしてみることにしました。

パフォーマンスを監視することも含めて、いくつかのテストを行うことにしました。

まず最初に、これがテストベッドです。デリゲートを持つクラスと、それを消費する別のクラスです。

class EventProducer
{
    public void Raise()
    {
        var handler = EventRaised;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    public event EventHandler EventRaised;
}

class Counter
{
    long count = 0;
    EventProducer producer = new EventProducer();

    public void Count()
    {
        producer.EventRaised += CountEvent;
        producer.Raise();
        producer.EventRaised -= CountEvent;
    }

    public void CountWithNew()
    {
        producer.EventRaised += new EventHandler(CountEvent);
        producer.Raise();
        producer.EventRaised -= new EventHandler(CountEvent);
    }

    private void CountEvent(object sender, EventArgs e)
    {
        count++;
    }
}

まず最初に、生成されたILを見ます。

.method public hidebysig instance void Count() cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
    L_0006: ldarg.0 
    L_0007: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
    L_000d: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
    L_0012: callvirt instance void DelegateTest.Program/EventProducer::add_EventRaised(class [mscorlib]System.EventHandler)
    L_0017: ldarg.0 
    L_0018: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
    L_001d: callvirt instance void DelegateTest.Program/EventProducer::Raise()
    L_0022: ldarg.0 
    L_0023: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
    L_0028: ldarg.0 
    L_0029: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
    L_002f: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
    L_0034: callvirt instance void DelegateTest.Program/EventProducer::remove_EventRaised(class [mscorlib]System.EventHandler)
    L_0039: ret 
}

.method public hidebysig instance void CountWithNew() cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
    L_0006: ldarg.0 
    L_0007: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
    L_000d: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
    L_0012: callvirt instance void DelegateTest.Program/EventProducer::add_EventRaised(class [mscorlib]System.EventHandler)
    L_0017: ldarg.0 
    L_0018: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
    L_001d: callvirt instance void DelegateTest.Program/EventProducer::Raise()
    L_0022: ldarg.0 
    L_0023: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
    L_0028: ldarg.0 
    L_0029: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
    L_002f: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
    L_0034: callvirt instance void DelegateTest.Program/EventProducer::remove_EventRaised(class [mscorlib]System.EventHandler)
    L_0039: ret 
}

というわけで、たしかにこれらは同一のILを生成することが判明しました。 私はもともと間違っていたのです。 しかし、それは ではない . このあたり、話がそれてしまうかもしれませんが、イベントやデリゲートについて語るときには、このあたりも含めて考えることが大切なのではないでしょうか。

異なるデリゲートを作成し、比較することは安くはありません。

これを書いたときは、最初の構文でメソッド群をデリゲートとしてキャストできると思っていたのですが、ただの変換であることがわかりました。 しかし、実際に を保存する をデリゲートとして保存する場合は全く異なります。 これをコンシューマーに追加すると

class Counter
{
    EventHandler savedEvent;

    public Counter()
    {
        savedEvent = CountEvent;
    }

    public void CountSaved()
    {
        producer.EventRaised += savedEvent;
        producer.Raise();
        producer.EventRaised -= savedEvent;
    }
}

これには 非常に 他の 2 つとは性能面で異なる特性を持っていることがわかります。

static void Main(string[] args)
{
    const int TestIterations = 10000000;

    TimeSpan countTime = TestCounter(c => c.Count());
    Console.WriteLine("Count: {0}", countTime);

    TimeSpan countWithNewTime = TestCounter(c => c.CountWithNew());
    Console.WriteLine("CountWithNew: {0}", countWithNewTime);

    TimeSpan countSavedTime = TestCounter(c => c.CountSaved());
    Console.WriteLine("CountSaved: {0}", countSavedTime);

    Console.ReadLine();
}

static TimeSpan TestCounter(Action<Counter> action, int iterations)
{
    var counter = new Counter();
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < TestIterations; i++)
        action(counter);
    sw.Stop();
    return sw.Elapsed;
}

というような結果が一貫して返ってきます。

Count: 00:00:02.4742007
CountWithNew: 00:00:02.4272702
CountSaved: 00:00:01.9810367

それは、ほぼ 20% 保存されたデリゲートを使用する場合と、新しいものを作成する場合の差です。

もちろん、すべてのプログラムがこれほど多くのデリゲートをわずかな時間で追加したり削除したりするわけではありませんが、ライブラリクラスを書いている場合、つまり予測できない方法で使用される可能性があるクラスを書いている場合は、この違いを心に留めておく必要があるでしょう。 を追加し イベントを追加したり削除したりする必要がある場合 (そして、私は個人的に、これを行うコードをたくさん書いてきました)、この違いを心に留めておきたいと思います。

ということで、結論としては SomeEvent += new EventHandler(NamedMethod) と書くのと同じことになります。 SomeEvent += NamedMethod . しかし,もし を削除します。 そのイベントハンドラを後で にする必要があります。 を保存します。 デリゲート . たとえ Delegate クラスには、追加したデリゲートとは参照元が異なるデリゲートを削除できる特別なコードがありますが、これを実行するためには自明ではない量の作業を行わなければなりません。

もしデリゲートを保存しないのであれば、それは何の違いもありません - コンパイラはとにかく新しいデリゲートを作成してしまいます。