C# 事件处理程序中空检入的使用

C# 事件处理程序中空检入的使用,c#,C#,检查事件处理程序是否为null时,是否按每个线程执行 确保有人正在收听活动的方式如下: EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen); private void OnEventSeven() { var handler = EventSeven; if (handler != null) { handler(this, EventArgs.Empty); } } 如果我在上面的检查

检查事件处理程序是否为null时,是否按每个线程执行

确保有人正在收听活动的方式如下:

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);
private void OnEventSeven()
{
    var handler = EventSeven;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}
如果我在上面的检查null的模式下添加代码,那么为什么我需要null检查()。我错过了什么


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

在启动事件处理程序之前检查它始终是一种良好的做法。我这样做,即使我最初“保证”自己,它总是设置。如果我以后更改此选项,则不必检查所有事件触发。因此,对于每个事件,我都有一个附带的OnXXX方法,如下所示:

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);
private void OnEventSeven()
{
    var handler = EventSeven;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}
如果事件处理程序对您的类是公共的,这一点尤其重要,因为外部调用方可以随意添加和删除事件处理程序。

如果您是指:

public static void OnEventSeven(DivBySevenEventArgs e)
    {
        if(EventSeven!=null)
            EventSeven(new object(),e);
    }    
一段代码,那么答案是:

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

规则是:


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

问题是,如果没有人订阅事件,则该事件为空。并且不能对null调用。我想到了三种方法:

  • 检查空值(见下文)
  • 添加一个“不做任何事情”处理程序:
    public event事件处理程序MyEvent=delegate{}
  • 使用扩展方法(见下文)
检查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);
    }

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

我恐怕真的不清楚您的意思,但是如果委托可能是空的,您需要在每个线程上分别检查它。通常您会:

public void OnSeven()
{
    DivBySevenHandler handler = EventSeven;
    if (handler != null)
    {
        handler(...);
    }
}
这确保了即使在
OnSeven()
过程中
EventSeven
发生更改,也不会出现
NullReferenceException

但是如果您确实有一个订阅的处理程序,那么您不需要空检查,这是对的。这可以在C#2中使用“无操作”处理程序轻松完成:

另一方面,如果您可能从不同的线程获得订阅,您可能需要某种类型的锁定,以确保您拥有“最新”的处理程序集。我有一个能帮上忙的方法——尽管通常我建议尽量避免使用它

就垃圾收集而言,事件发布服务器最终会引用事件订阅服务器(即处理程序的目标)。只有当发布服务器的寿命比订阅服务器长时,这才是一个问题。

使用它可以在编译后的步骤中调整已编译的程序集。这允许您将“方面”应用于代码,从而解决交叉关注点

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

它的使用非常简单:

[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 );
    }
}
//
///当应用于程序集或类时,初始化所有事件处理程序()成员的方面
///在具有空委托的类中,以防止。
/// 
///史蒂文·杰里斯
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Event)]
[MulticastAttributeUsage(MulticastTargets.Event,AllowMultiple=false)]
[AspectTypeDependencyAction.comment,typeof(InitializeEventHandlerAttribute))]
[可序列化]
公共类初始化EventHandlerAttribute:EventLevelAspect
{
[非串行化]
动作(加法运算);
[OnMethodEntryAdvice,MethodPointcut(“SelectConstructors”)]
构造入口上的公共无效(MethodExecutionArgs args)
{
_addEmptyEventHandler(args.Instance);
}
//ReSharper禁用UnusedMember.Local
IEnumerable选择器构造函数(EventInfo目标)
{
返回target.DeclaringType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
//ReSharper还原未使用的成员。本地
公共覆盖无效运行时初始化(EventInfo EventInfo)
{
base.RuntimeInitialize(eventInfo);
//构造合适的空事件处理程序。
MethodInfo delegateInfo=DelegateHelper.MethodInfoFromDelegateType(eventInfo.EventHandlerType);
ParameterExpression[]parameters=delegateInfo.GetParameters().Select(p=>Expression.Parameter(p.ParameterType)).ToArray();
委派成员
=Expression.Lambda(eventInfo.EventHandlerType,Expression.Empty(),“EmptyDelegate”,true,parameters).Compile();
//创建一个将空处理程序添加到实例的委托。
_addEmptyEventHandler=instance=>eventInfo.AddEventHandler(实例,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 );
}
//
///委托的Invoke方法的名称。
/// 
常量字符串InvokeMethod=“Invoke”;
/// 
///获取指定委托类型的方法信息。
/// 
///要获取其信息的委托类型。
///给定委托类型的方法信息。
公共静态MethodInfo MethodInfoFromDelegateType(类型delegateType)
{
Contract.Requires(delegateType.IsSubclassOf(typeof(MulticastDelegate)),“给定类型应为委托。”);
返回delegateType.GetMethod(InvokeMethod);
}

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

现在可以替换此选项:

var handler = EventSeven;

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

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