在事件处理程序中使用null check

时间:2009-03-23 09:11:49

标签: c#

当检查事件处理程序是否为null时,是否基于每个线程完成?

确保有人正在听这个事件是这样的:

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);

如果我在上面检查null的模式之后添加代码,那么为什么我需要空检查(code taken from this site)。我错过了什么?

此外,事件和GC的规则是什么?

6 个答案:

答案 0 :(得分:51)

问题是如果没有人订阅该事件,则为null。并且你无法调用null。跳过三种方法:

  • 检查null(见下文)
  • 添加“无所事事”处理程序:public event EventHandler MyEvent = delegate {};
  • 使用扩展方法(见下文)

当检查null时,为了线程安全,你必须理论上首先捕获委托引用(如果它在check和invoke之间发生变化):

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

扩展方法具有不常见的属性,它们可以在空实例上调用...

    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);

并且它都是空安全的(通过检查)和线程安全的(通过仅读取一次引用)。

答案 1 :(得分:48)

我真的不清楚你的意思我害怕,但是如果委托的可能性为null,你需要在每个线程上单独检查它。通常你会这样做:

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

这可确保即使EventSevenOnSeven()期间发生变化,您也无法获得NullReferenceException

但是如果你肯定有一个订阅的处理程序,那么你是不对的。这可以通过“无操作”处理程序在C#2中轻松完成:

public event DivBySevenHandler EventSeven = delegate {};

另一方面,如果您可能从各种线程获得订阅,那么可能想要某种锁定以确保您拥有“最新”的处理程序集。我有一个example in my threading tutorial可以提供帮助 - 尽管我通常建议尽量避免要求它。

就垃圾收集而言,事件发布者最终会引用事件订阅者(即处理程序的目标)。如果发布者的寿命要长于订阅者,那么这只是一个问题。

答案 2 :(得分:27)

我想附加一些关于C#6.0-Syntax的简短信息:

现在可以替换它:

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);

<小时/> See MSDN for more information about the null-conditional operator
请参阅this blog关于表情健全的成员

答案 3 :(得分:2)

在触发事件处理程序之前检查它是一种好习惯。我这样做即使我最初“保证”自己总是设置它。如果我以后改变这个,我不必检查我的所有事件。因此,对于每个事件,我总是有一个伴随的OnXXX方法:

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

如果事件处理程序对您的类是公共的,这一点尤其重要,因为外部调用者可以随意添加和删除事件处理程序。

答案 4 :(得分:-1)

如果你的意思是:

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

一段代码,然后答案是:

如果没有人订阅“EventSeven”事件处理程序,那么你将在“EventSeven(new object(),e)上获得一个空引用异常;”

规则:

订户负责添加处理程序(+ =)并在他不想再接收事件时将其删除( - =)。垃圾收集遵循默认规则,如果不再引用对象,则可以清除它。

答案 5 :(得分:-1)

使用PostSharp可以在编译后步骤中调整编译的程序集。这允许您将“方面”应用于代码,解决交叉问题。

虽然null检查或空委托初始化可能是一个非常小的问题,但我写了一个方面,通过向程序集中的所有事件添加一个空委托来解决它。

它的使用非常简单:

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

discussed the aspect in detail on my blog。如果你有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 );
}