当检查事件处理程序是否为null时,是否基于每个线程完成?
确保有人正在听这个事件是这样的:
EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);
如果我在上面检查null的模式之后添加代码,那么为什么我需要空检查(code taken from this site)。我错过了什么?
此外,事件和GC的规则是什么?
答案 0 :(得分:51)
问题是如果没有人订阅该事件,则为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(...);
}
}
这可确保即使EventSeven
在OnSeven()
期间发生变化,您也无法获得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
答案 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 );
}