[c#] 이벤트 선언에 익명의 빈 대리자를 추가하는 데 단점이 있습니까?

이 관용구에 대한 몇 가지 언급을 보았습니다 ( SO 포함 ) :

// Deliberately empty subscriber
public event EventHandler AskQuestion = delegate {};

장점은 분명합니다. 이벤트를 발생시키기 전에 null을 확인할 필요가 없습니다.

그러나 단점이 있는지 이해하고 싶습니다. 예를 들어, 널리 사용되고 있고 유지 관리 문제를 일으키지 않을만큼 투명한 것입니까? 빈 이벤트 구독자 호출로 인해 눈에 띄는 성능 저하가 있습니까?



답변

유일한 단점은 여분의 빈 델리게이트를 호출 할 때 매우 약간의 성능 저하입니다. 그 외에는 유지 보수 패널티 또는 기타 단점이 없습니다.


답변

성능 오버 헤드를 유발하는 대신 확장 방법사용하여 두 문제를 모두 완화 하는 것이 좋습니다 .

public static void Raise(this EventHandler handler, object sender, EventArgs e)
{
    if(handler != null)
    {
        handler(sender, e);
    }
}

정의 된 후에는 다시 null 이벤트 검사를 수행 할 필요가 없습니다.

// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);


답변

시스템의 이벤트를 많이 사용하고 성능이 중요하다 , 당신은 확실히 적어도하기를 원할 것입니다 고려 이렇게하지. 빈 델리게이트로 이벤트를 발생시키는 비용은 먼저 null 확인을 사용하여 이벤트를 발생시키는 비용의 약 두 배입니다.

내 컴퓨터에서 벤치 마크를 실행하는 몇 가지 수치는 다음과 같습니다.

For 50000000 iterations . . .
No null check (empty delegate attached): 530ms
With null check (no delegates attached): 249ms
With null check (with delegate attached): 452ms

그리고 다음은 이러한 수치를 얻는 데 사용한 코드입니다.

using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        public event EventHandler<EventArgs> EventWithDelegate = delegate { };
        public event EventHandler<EventArgs> EventWithoutDelegate;

        static void Main(string[] args)
        {
            //warm up
            new Program().DoTimings(false);
            //do it for real
            new Program().DoTimings(true);

            Console.WriteLine("Done");
            Console.ReadKey();
        }

        private void DoTimings(bool output)
        {
            const int iterations = 50000000;

            if (output)
            {
                Console.WriteLine("For {0} iterations . . .", iterations);
            }

            //with anonymous delegate attached to avoid null checks
            var stopWatch = Stopwatch.StartNew();

            for (var i = 0; i < iterations; ++i)
            {
                RaiseWithAnonDelegate();
            }

            stopWatch.Stop();

            if (output)
            {
                Console.WriteLine("No null check (empty delegate attached): {0}ms", stopWatch.ElapsedMilliseconds);
            }


            //without any delegates attached (null check required)
            stopWatch = Stopwatch.StartNew();

            for (var i = 0; i < iterations; ++i)
            {
                RaiseWithoutAnonDelegate();
            }

            stopWatch.Stop();

            if (output)
            {
                Console.WriteLine("With null check (no delegates attached): {0}ms", stopWatch.ElapsedMilliseconds);
            }


            //attach delegate
            EventWithoutDelegate += delegate { };


            //with delegate attached (null check still performed)
            stopWatch = Stopwatch.StartNew();

            for (var i = 0; i < iterations; ++i)
            {
                RaiseWithoutAnonDelegate();
            }

            stopWatch.Stop();

            if (output)
            {
                Console.WriteLine("With null check (with delegate attached): {0}ms", stopWatch.ElapsedMilliseconds);
            }
        }

        private void RaiseWithAnonDelegate()
        {
            EventWithDelegate(this, EventArgs.Empty);
        }

        private void RaiseWithoutAnonDelegate()
        {
            var handler = EventWithoutDelegate;

            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }
}


답변

/ lot /으로 수행하는 경우 델리게이트 인스턴스의 볼륨을 줄이기 위해 재사용 할 단일 정적 / 공유 빈 델리게이트를 원할 수 있습니다. 컴파일러는 어쨌든 (정적 필드에서) 이벤트마다이 델리게이트를 캐시하므로 이벤트 정의당 하나의 델리게이트 인스턴스 일 뿐이므로 절감 은 아니지만 가치가있을 수 있습니다.

물론 각 클래스의 인스턴스 별 필드는 동일한 공간을 차지합니다.

internal static class Foo
{
    internal static readonly EventHandler EmptyEvent = delegate { };
}
public class Bar
{
    public event EventHandler SomeEvent = Foo.EmptyEvent;
}

그 외에는 괜찮아 보입니다.


답변

극단적 인 상황을 제외하고는 의미있는 성능 저하가 없습니다.

그러나 C # 6.0에서는 언어가 null 일 수있는 대리자를 호출하는 대체 구문을 제공하기 때문에이 트릭은 관련성이 떨어집니다.

delegateThatCouldBeNull?.Invoke(this, value);

위의 null 조건부 연산자 ?.는 null 검사와 조건부 호출을 결합합니다.


답변

빈 대리자는 스레드로부터 안전하지만 null 검사는 그렇지 않다는 것을 이해합니다.


답변

나는 당신이 다음과 같은 것을하도록 유혹하기 때문에 약간 위험한 구조라고 말하고 싶습니다.

MyEvent(this, EventArgs.Empty);

클라이언트가 예외를 던지면 서버도 예외를 함께합니다.

따라서 아마도 다음과 같이 할 수 있습니다.

try
{
  MyEvent(this, EventArgs.Empty);
}
catch
{
}

그러나 여러 구독자가 있고 한 구독자가 예외를 throw하면 다른 구독자는 어떻게됩니까?

이를 위해 null 검사를 수행하고 구독자 측의 예외를 삼키는 몇 가지 정적 도우미 메서드를 사용해 왔습니다 (idesign에서 가져온 것입니다).

// Usage
EventHelper.Fire(MyEvent, this, EventArgs.Empty);


public static void Fire(EventHandler del, object sender, EventArgs e)
{
    UnsafeFire(del, sender, e);
}
private static void UnsafeFire(Delegate del, params object[] args)
{
    if (del == null)
    {
        return;
    }
    Delegate[] delegates = del.GetInvocationList();

    foreach (Delegate sink in delegates)
    {
        try
        {
            sink.DynamicInvoke(args);
        }
        catch
        { }
    }
}