25

이벤트 처리기가 null인지 확인하는 경우 스레드별로이 작업이 수행됩니까?

누군가가 이벤트를 듣고 있는지 확인하는 것은 다음과 같이 수행됩니다.

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);

위의 패턴을 따라 코드를 추가하면 null을 확인한 다음 null 체크가 필요한 이유는 무엇입니까?이 사이트에서 가져온 코드). 내가 뭘 놓치고 있니?

또한 이벤트 및 GC의 규칙은 무엇입니까?


6 답변


47

당신이 의미하는 바가 무엇인지는 분명치 않습니다. 그러나 델리게이트가 null 일 가능성이 있다면, 각 스레드에서 개별적으로 체크해야합니다. 일반적으로 다음을 수행합니다.

public void OnSeven()
{
    DivBySevenHandler handler = EventSeven;
    if (handler != null)
    {
        handler(...);
    }
}

이렇게하면EventSeven과정 중에 변화OnSeven()너는NullReferenceException.

하지만 구독 처리기가 확실히있는 경우 null 체크가 필요하지 않습니다. 이것은 C #2에서 "no-op"핸들러를 사용하여 쉽게 수행 할 수 있습니다.

public event DivBySevenHandler EventSeven = delegate {};

반면에, 당신은다양한 스레드로부터 서브 스크립 션을 얻을 수있는 경우, "최신"핸들러 세트가 있는지 확인하기 위해 일종의 잠금을 원합니다. 나는내 스레딩 튜토리얼의 예제도움이 될 수 있습니다 - 일반적으로 나는 그것을 요구하지 않으려 고 노력하는 것이 좋습니다.

가비지 콜렉션 측면에서, 이벤트발행자이벤트에 대한 참조로 끝난다.구독자(즉, 핸들러의 목표). 게시자가 구독자보다 오래 거주하려는 경우에만 문제가됩니다.


  • (OP /리스트 용) 마지막 포인트가됩니다.특히정적 이벤트의 참 (정적 필드는 수집 대상이되지 않기 때문에) 정적 이벤트는 이러한 이유로 정확하게 피하는 것이 가장 좋습니다. - Marc Gravell
  • void EventDelegate의 호출이 nop으로 명시 적으로 정의 될 것이라는 점을 제외하고 void delegate / except /와 동일한 의미를 가질 Delegate의 EventDelegate 카테고리를 갖는 데 문제가 있었는지 궁금합니다. - supercat
  • 이건 어떤가요EventSeven?.Invoke(....). 스레드 안전합니까? - Hossein Narimani Rad
  • @HosseinNarimaniRad : 답안의 예와 마찬가지로 안전합니다. 그렇습니다. C #의 메모리 모델은 그다지 명확하지 않지만 기본적으로는 안전하다고 생각합니다. - Jon Skeet

51

문제는 아무도 이벤트에 가입하지 않으면 null입니다. 그리고 null에 대해 호출 할 수 없습니다. 3 가지 접근법이 떠오른다.

  • null을 확인하십시오 (아래 참조).
  • "do nothing"처리기를 추가하십시오.public event EventHandler MyEvent = delegate {};
  • 확장 메소드 사용 (아래 참조)

null을 검사 할 때 스레드로부터 안전하려면 반드시이론에 의하면위임 참조를 먼저 캡처하십시오 (체크와 호출 사이에서 변경되는 경우).

protected virtual void OnMyEvent() {
    EventHandler handler = MyEvent;
    if(handler != null) handler(this, EventArgs.Empty);
}

확장 메서드는 null 인스턴스에서 호출 할 수있는 특이한 속성을가집니다.

    public static void SafeInvoke(this EventHandler handler, object sender)
    {
        if (handler != null) handler(sender, EventArgs.Empty);
    }
    public static void SafeInvoke<T>(this EventHandler<T> handler,
        object sender, T args) where T : EventArgs
    {
        if (handler != null) handler(sender, args);
    }

다음을 호출 할 수 있습니다.

MyEvent.SafeInvoke(this);

검사를 통해 널 (null) - 안전하고 스레드 안전 (참조를 한 번만 읽음)합니다.


  • 스택의 implict 복사본을 통한 스레드 안전성 - ShuggyCoUk
  • 좋은 설명. 읽기가 끝나면 대상 (읽기 참조 된 객체)이 자유롭게 바뀔 수 있기 때문에 "읽는 것만으로 ..."라고 말하면 스레드로부터 안전합니다. 하지만 어떻게 한 번 참조를 읽습니까? " 아마도 나는 뭔가를 따라 가지 않았다. - dotnetdev
  • @dotnetdev - 메서드 인수로 전달하기 때문에; 현재 값을 읽는다.스택에. SafeInvoke 내부에서는 스택에 사본 만 표시됩니다 (원본은 200 번 업데이트 될 수 있으며 표시되지 않습니다). 그것은 대리자가 불변이므로 여기에 도움이됩니다. - Marc Gravell
  • ... 구독을 취소하면 새 대리자가 만들어지고 새 인스턴스를 가리 키도록 필드가 변경됩니다. "if (MyEvent! = null) MyEvent (...)"와 대조적으로, - 분명히 거기에 2 읽습니다. - Marc Gravell
  • @VoodooChild는 태양을 분류 할 때 자신의 이벤트에 가입하는 것은 나쁜 습관으로 간주됩니다. 상속 할 때 재정의하는 것이 좋습니다. Tht는 모두 다. - Marc Gravell

23

C #6.0에 대한 간단한 정보를 추가하고 싶습니다. - 구문 :

이제 이것을 대체 할 수 있습니다 :

var handler = EventSeven;

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

이걸로 :

handler?.Invoke(this, EventArgs.Empty);


그것과 결합표현 바디 멤버, 다음 코드를 줄일 수 있습니다 :

protected virtual void OnMyEvent()
{
    EventHandler handler = MyEvent;
    handler?.Invoke(this, EventArgs.Empty);
}

~에 이르기까지짧막 한 농담:

protected virtual void OnMyEvent() => MyEvent?.Invoke(this, EventArgs.Empty);


null 조건부 연산자에 대한 자세한 내용은 MSDN (영문)을 참조하십시오.

만나다이 블로그표현 바디 멤버들



2

이벤트 처리기를 시작하기 전에 이벤트 처리기를 확인하는 것이 좋습니다. 나는 처음에 그것이 항상 설정된다는 것을 나 자신에게 "보증"한다해도 이것을한다. 나중에 이걸 변경하면 내 모든 이벤트 발사를 확인할 필요가 없습니다. 그래서 각 이벤트마다 항상 다음과 같은 OnXXX 메서드가 있습니다.

private void OnEventSeven()
{
    var handler = EventSeven;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}

외부 호출자가 마음대로 이벤트 처리기를 추가하거나 제거 할 수 있기 때문에 이벤트 처리기가 클래스에 공용 인 경우 특히 중요합니다.


  • 그것은 스레드로부터 안전하지 않습니다. 마지막 핸들러가 < if > 가 평가되면 NullReferenceException으로 끝날 수 있습니다. - Jon Skeet
  • 이봐 요, 어떻게 그렇게 많은 사람들이 스레드 유엔 안전성을 보지 못했습니까? 웨이크 업 콜에 감사드립니다! - Peter Lillevold
  • 우리 중 많은 사람들이 같은 (안티) 패턴을 사용하고있는 것처럼 보입니다. 그러나이 경우 Peter는 Marc이 수행 한 것처럼 수행합니다 (참조를 캡처하지만 암시 적 유형 사용). 마크가 쓴 것과 피터의 차이점은 무엇입니까? - dotnetdev
  • @ 존 스킷 (Jeon Skeet) - 위의 마크 그래벨 (Marc Gravell)을 위와 같이 반복하여 다른 사람들은 이것이 스레드로부터 안전하지 않다고 확신하지는 않습니다. 대리자는 불변이므로, 소스 이벤트로부터 핸들러가 등록 해제되면 새로운 오브젝트가 작성됩니다. 이벤트로 만들어진 복사본은 여전히 원래 대리자를 참조합니다. - Reed Rector
  • @Reed : 스레드로부터 안전합니다.지금, 예. 댓글을 작성할 때가 아니 었습니다. 편집 로그를 확인하십시오. - Jon Skeet

-1

당신이 이것을 의미한다면 :

public static void OnEventSeven(DivBySevenEventArgs e)
    {
        if(EventSeven!=null)
            EventSeven(new object(),e);
    }    

코드 조각이면 대답은 다음과 같습니다.

아무도 "EventSeven"이벤트 핸들러에 가입하지 않으면 "EventSeven (new object (), e);에 널 참조 예외가 발생합니다."

그리고 규칙 :

구독자는 더 이상 이벤트를 수신하지 않으려는 경우 처리기 (+ =)를 추가하고 제거 (- =) 할 책임이 있습니다. 객체가 더 이상 참조 될 수 없으면 객체를 정리할 수 있지만 가비지 수집은 기본 규칙에 따라 진행됩니다.


  • 그것은 스레드로부터 안전하지 않습니다. 마지막 핸들러가 < if > 가 평가되면 NullreferenceException으로 끝날 수 있습니다. - Jon Skeet
  • 사실 ...이 예제는 모든 곳에서 (그리고 나는 그것을 사용한다.) 결코 이것의 스레드 안전성에 서 있지 않았다! - thijs
  • 예외적으로, 발신자가 이벤트를 시작하는 객체 (예 :this), 임의로 새로 생성 된 객체가 아닌? - Rowland Shaw
  • @Rowland - 동의 함 - Marc Gravell
  • " 객체가 더 이상 참조되지 않는 경우 " ... collcetion에 적합하지 않은 객체에 의해; 참조 b와 참조 b가 참조 a 인 경우 다른 참조 할 수없는 경우에도 여전히 참조 할 수 있습니다. - Marc Gravell

-1

사용포스트 샤프컴파일 후 단계에서 컴파일 된 어셈블리를 조정할 수 있습니다. 이를 통해 코드에 'aspect'를 적용하여 교차 절단 문제를 해결할 수 있습니다.

null 검사 또는 빈 대리자 초기화가 매우 사소한 문제 일지라도 어셈블리의 모든 이벤트에 빈 대리자를 추가하여이를 해결하는 측면을 썼습니다.

사용법은 매우 쉽습니다.

[assembly: InitializeEventHandlers( AttributeTargetTypes = "Main.*" )]
namespace Main
{
   ...
}

나는내 블로그에 세부 사항을 논의했다.. PostSharp를 사용하는 경우 다음과 같은 측면이 있습니다.

/// <summary>
///   Aspect which when applied on an assembly or class, initializes all the event handlers (<see cref="MulticastDelegate" />) members
///   in the class(es) with empty delegates to prevent <see cref="NullReferenceException" />'s.
/// </summary>
/// <author>Steven Jeuris</author>
[AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Event )]
[MulticastAttributeUsage( MulticastTargets.Event, AllowMultiple = false )]
[AspectTypeDependency( AspectDependencyAction.Commute, typeof( InitializeEventHandlersAttribute ) )]
[Serializable]
public class InitializeEventHandlersAttribute : EventLevelAspect
{
    [NonSerialized]
    Action<object> _addEmptyEventHandler;


    [OnMethodEntryAdvice, MethodPointcut( "SelectConstructors" )]
    public void OnConstructorEntry( MethodExecutionArgs args )
    {
        _addEmptyEventHandler( args.Instance );
    }

    // ReSharper disable UnusedMember.Local
    IEnumerable<ConstructorInfo> SelectConstructors( EventInfo target )
    {
        return target.DeclaringType.GetConstructors( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
    }
    // ReSharper restore UnusedMember.Local

    public override void RuntimeInitialize( EventInfo eventInfo )
    {
        base.RuntimeInitialize( eventInfo );

        // Construct a suitable empty event handler.
        MethodInfo delegateInfo = DelegateHelper.MethodInfoFromDelegateType( eventInfo.EventHandlerType );
        ParameterExpression[] parameters = delegateInfo.GetParameters().Select( p => Expression.Parameter( p.ParameterType ) ).ToArray();
        Delegate emptyDelegate
            = Expression.Lambda( eventInfo.EventHandlerType, Expression.Empty(), "EmptyDelegate", true, parameters ).Compile();

        // Create a delegate which adds the empty handler to an instance.
        _addEmptyEventHandler = instance => eventInfo.AddEventHandler( instance, emptyDelegate );
    }
}

... 그리고 그것을 사용하는 도우미 메서드 :

/// <summary>
///   The name of the Invoke method of a Delegate.
/// </summary>
const string InvokeMethod = "Invoke";


/// <summary>
///   Get method info for a specified delegate type.
/// </summary>
/// <param name = "delegateType">The delegate type to get info for.</param>
/// <returns>The method info for the given delegate type.</returns>
public static MethodInfo MethodInfoFromDelegateType( Type delegateType )
{
    Contract.Requires( delegateType.IsSubclassOf( typeof( MulticastDelegate ) ), "Given type should be a delegate." );

    return delegateType.GetMethod( InvokeMethod );
}

연결된 질문


관련된 질문

최근 질문