이벤트 처리기를 등록 취소하는 방법
코드 검토에서 이벤트 처리기를 등록 취소하기 위해이 (단순화 된) 코드 조각을 우연히 발견했습니다.
Fire -= new MyDelegate(OnFire);
이전에 등록 된 적이없는 새 델리게이트를 생성하기 때문에 이것이 이벤트라고 생각의 등록을 취소하지 않았습니다. 그러나 MSDN을 검색하면 관용구를 사용하는 여러 코드 샘플을 찾았습니다.
그래서 실험을 시작했습니다.
internal class Program
{
public delegate void MyDelegate(string msg);
public static event MyDelegate Fire;
private static void Main(string[] args)
{
Fire += new MyDelegate(OnFire);
Fire += new MyDelegate(OnFire);
Fire("Hello 1");
Fire -= new MyDelegate(OnFire);
Fire("Hello 2");
Fire -= new MyDelegate(OnFire);
Fire("Hello 3");
}
private static void OnFire(string msg)
{
Console.WriteLine("OnFire: {0}", msg);
}
}
놀랍게도 다음과 같은 일이 발생했습니다.
Fire("Hello 1");
예상대로 두 개의 메시지를 생성했습니다.Fire("Hello 2");
하나의 메시지를 생성했습니다!
이로 인해new
대의원 등록 취소가 작동 확신이 들었습니다 !Fire("Hello 3");
던 많이NullReferenceException
.
보여 뗄 수없는 코드Fire
인null
이벤트를 등록 해제 한 후입니다.
이벤트 나 델리게이트의 경우 컴파일러가이면에서 많은 코드를 생성한다는 것을 알고 있습니다. 그러나 나는 여전히 내 추론이 잘못된 이유를 이해하지 못합니다.
내가 무엇을 놓치고 있습니까?
추가 질문 : 사실 Fire
이다 null
등록 된 이벤트가 없을 때, 나는 이벤트가 해고 사방에 대한 확인이 결론을 내릴 필요 null
하다.
이벤트 이벤트 호출 Delegate.Combine
을 제거하는 동안 이벤트 호출을 추가하는 C # 컴파일러의 기본 구현 Delegate.Remove
:
Fire = (MyDelegate) Delegate.Remove(Fire, new MyDelegate(Program.OnFire));
Framework의 구현은 자체를 Delegate.Remove
보지 않고 MyDelegate
대리자가 개체를 참조하는 방법 ( Program.OnFire
)을 확인합니다. 기존의 MyDelegate
기존 이벤트 처리기를 구독 취소 할 때 새 개체 를 만드는 것이 완벽하게 안전 합니다. 이 때문에 C # 컴파일러를 사용하면 이벤트 처리기를 추가 / 제거 할 때 축약 형 구문 (배후에서 동일한 코드를 생성 함)을 사용할 수 있습니다. 다음 new MyDelegate
부분을 생략 할 수 있습니다 .
Fire += OnFire;
Fire -= OnFire;
마지막 대리자가 이벤트 처리기에서 제거하면 Delegate.Remove
null을 반환합니다. 알다시피 이벤트를 발생시키기 전에 null에 대해 이벤트를 확인하는 것이 중요합니다.
MyDelegate handler = Fire;
if (handler != null)
handler("Hello 3");
다른 행사에서 이벤트 처리기를 구독 취소하여 가능한 경쟁 조건을 방어하기 위해 임시 지역 변수에 할당됩니다. ( 이벤트 핸들러를 로컬 변수에 할당하는 스레드 안전성에, 대한 자세한 내용 은 내 블로그 게시물 을 참조하십시오 .)이 문제를 방지하는 또 다른 방법 은 항상 구독되는 빈 델리게이트를 만드는 것입니다 . 이것은 약간 더 많은 메모리를 사용하지만 이벤트는 null이 될 수 있습니다.
public static event MyDelegate Fire = delegate { };
델리게이트를 실행하기 전에 항상 타겟이 없는지 (값이 null인지) 확인해야합니다. 앞서 말했듯이이를 수행하는 한 가지 방법은 제거되지 않는 익명 메서드로 구독하는 것입니다.
public event MyDelegate Fire = delegate {};
그러나 이것은 NullReferenceExceptions를 피하기위한 해킹 일뿐입니다.
단순히 호출하기 전에 델리게이트가 null인지 확인하는 것은 다른 스레드가 null 검사 후 등록을 취소하고 호출 할 때 null로 만들 수 있기 때문에 스레드로부터 안전하지 않습니다. 다른 해결책은 대리자를 임시 변수에 복사하는 것입니다.
public event MyDelegate Fire;
public void FireEvent(string msg)
{
MyDelegate temp = Fire;
if (temp != null)
temp(msg);
}
불행히도 JIT 컴파일러는 코드를 최적화하고 임시 변수를 제거하며 원래 대리자를 사용할 수 있습니다. (Juval Lowy-프로그래밍 .NET 구성 요소에 따라)
따라서이 문제를 방지하려면 대리자를 매개 변수로 받아들이는 메서드를 사용할 수 있습니다.
[MethodImpl(MethodImplOptions.NoInlining)]
public void FireEvent(MyDelegate fire, string msg)
{
if (fire != null)
fire(msg);
}
MethodImpl (NoInlining) 속성이 없으면 JIT 컴파일러가 메소드를 인라인하여 쓸모 없게 만들 수 있습니다. 델리게이트는 불변이므로이 구현은 스레드로부터 안전합니다. 이 방법을 다음과 같이 사용할 수 있습니다.
FireEvent(Fire,"Hello 3");
참고 URL : https://stackoverflow.com/questions/292820/how-to-correctly-unregister-an-event-handler
'ProgramingTip' 카테고리의 다른 글
.map () 자바 펼쳐 ES6 맵? (0) | 2020.11.21 |
---|---|
const, let 및 var의 v8 JavaScript 성능 영향? (0) | 2020.11.21 |
NULL을 허용하는 MySQL 외래 키? (0) | 2020.11.21 |
취소하지 않는 것이 나쁜가요? (0) | 2020.11.21 |
열 너비가 고정 된 LaTeX 테이블의 셀 내용을 중앙에 배치하는 방법은 무엇입니까? (0) | 2020.11.21 |