이벤트 선언에 익명의 빈 대리자를 추가하는 데 단점이 있습니까?
이 관용구에 대한 몇 가지 언급을 보았습니다 ( 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 /으로 수행하는 경우 델리게이트 인스턴스의 볼륨을 줄이기 위해 재사용 할 단일 정적 / 공유 빈 델리게이트를 원할 수 있습니다. 컴파일러는 어쨌든 (정적 필드에서) 이벤트마다이 델리게이트를 캐시하므로 이벤트 정의당 하나의 델리게이트 인스턴스 일 뿐이므로 큰 절약 은 아니지만 가치가있을 수 있습니다.
The per-instance field in each class will still take the same space, of course.
i.e.
internal static class Foo
{
internal static readonly EventHandler EmptyEvent = delegate { };
}
public class Bar
{
public event EventHandler SomeEvent = Foo.EmptyEvent;
}
Other than that, it seems fine.
It is my understanding that the empty delegate is thread safe, whereas the null check is not.
I would say it's a bit of a dangerous construct, because it tempts you to do something like :
MyEvent(this, EventArgs.Empty);
If the client throws an exception, the server goes with it.
So then, maybe you do:
try
{
MyEvent(this, EventArgs.Empty);
}
catch
{
}
But, if you have multiple subscribers and one subscriber throws an exception, what happens to the other subscribers?
To that end, I've been using some static helper methods that do the null check and swallows any exception from the subscriber side (this is from 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
{ }
}
}
There is no meaningful performance penalty to talk about, except, possibly, for some extreme situations.
Note, however, that this trick becomes less relevant in C# 6.0, because the language provides an alternative syntax to calling delegates that may be null:
delegateThatCouldBeNull?.Invoke(this, value);
Above, null conditional operator ?. combines null checking with a conditional invocation.
Instead of "empty delegate" approach one can define a simple extension method to encapsulate the conventional method of checking event handler against null. It is described here and here.
One thing is missed out as an answer for this question so far: It is dangerous to avoid the check for the null value.
public class X
{
public delegate void MyDelegate();
public MyDelegate MyFunnyCallback = delegate() { }
public void DoSomething()
{
MyFunnyCallback();
}
}
X x = new X();
x.MyFunnyCallback = delegate() { Console.WriteLine("Howdie"); }
x.DoSomething(); // works fine
// .. re-init x
x.MyFunnyCallback = null;
// .. continue
x.DoSomething(); // crashes with an exception
The thing is: You never know who will use your code in which way. You never know, if in some years during a bug fix of your code the event/handler is set to null.
Always, write the if check.
Hope that helps ;)
ps: Thanks for the performance calculation.
pps: Edited it from a event case to and callback example. Thanks for the feedback ... I "coded" the example w/o Visual Studio and adjusted the example I had in mind to an event. Sorry for the confusion.
ppps: Do not know if it still fits to the thread ... but I think it is an important principle. Please also check another thread of stackflow
'developer tip' 카테고리의 다른 글
| redis-py : StrictRedis ()와 Redis ()의 차이점은 무엇입니까? (0) | 2020.09.25 |
|---|---|
| extract-text-webpack-plugin React를 사용할 때 창 정의되지 않음 오류 (0) | 2020.09.25 |
| 비틀어 진 파이썬 : 어디서부터 시작해야할까요? (0) | 2020.09.25 |
| ThreadLocal 변수의 성능 (0) | 2020.09.25 |
| write.table은 행 이름이있을 때 원하지 않는 선행 빈 열을 헤더에 씁니다. (0) | 2020.09.25 |