C# 在事件处理程序中使用空检查
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/672638/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Use of null check in event handler
提问by GurdeepS
When checking if an event handler is null, is this done on a per-thread basis?
当检查事件处理程序是否为空时,这是在每个线程的基础上完成的吗?
Ensuring someone is listening to the event is done like this:
确保有人正在收听事件是这样完成的:
EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);
If I add code following this pattern above where I check for null, then why would I need a null check (code taken from this site). What am I missing?
如果我在上面检查空值的地方添加遵循此模式的代码,那么为什么我需要空值检查(从该站点获取的代码)。我错过了什么?
Also, what's the rule with events and GC?
另外,事件和 GC 的规则是什么?
采纳答案by Jon Skeet
It's really not clear what you mean I'm afraid, but if there's the possibility of the delegate being null, you need to check that separately on each thread. Typically you'd do:
恐怕真的不清楚你的意思,但如果委托有可能为空,你需要在每个线程上分别检查。通常你会这样做:
public void OnSeven()
{
DivBySevenHandler handler = EventSeven;
if (handler != null)
{
handler(...);
}
}
This ensures that even if EventSeven
changes during the course of OnSeven()
you won't get a NullReferenceException
.
这确保即使EventSeven
在OnSeven()
您的过程中发生更改,您也不会获得NullReferenceException
.
But you're right that you don't need the null check if you've definitely got a subscribed handler. This can easily be done in C# 2 with a "no-op" handler:
但是你是对的,如果你确实有一个订阅的处理程序,你不需要空检查。这可以在 C# 2 中使用“无操作”处理程序轻松完成:
public event DivBySevenHandler EventSeven = delegate {};
On the other hand, you mightwant some sort of locking just to make sure that you've got the "latest" set of handlers, if you might get subscriptions from various threads. I have an example in my threading tutorialwhich can help - although usually I'd recommend trying to avoid requiring it.
另一方面,如果您可能从各种线程获得订阅,您可能需要某种锁定来确保您拥有“最新”的一组处理程序。我的线程教程中有一个示例可以提供帮助 - 尽管通常我会建议尽量避免使用它。
In terms of garbage collection, the event publisherends up with a reference to the event subscriber(i.e. the target of the handler). This is only a problem if the publisher is meant to live longer than the subscriber.
在垃圾收集方面,事件发布者最终获得了对事件订阅者(即处理程序的目标)的引用。如果发布者的寿命比订阅者长,这只是一个问题。
回答by Peter Lillevold
It is always good practice to check an event handler before firing it. I do this even if I initially "guarantee" myself that it is always set. If I later change this I don't have to check all my event firing. So for each event I always have an accompanying OnXXX method like this:
在触发之前检查事件处理程序始终是一个好习惯。即使我最初“保证”自己始终设置它,我也会这样做。如果我稍后更改此设置,则不必检查所有事件触发。所以对于每个事件,我总是有一个伴随的 OnXXX 方法,如下所示:
private void OnEventSeven()
{
var handler = EventSeven;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
This is especially important if the event handler is public to your class since external callers can add and remove event handlers at will.
如果事件处理程序对您的类是公开的,这一点尤其重要,因为外部调用者可以随意添加和删除事件处理程序。
回答by thijs
If you mean this:
如果你是这个意思:
public static void OnEventSeven(DivBySevenEventArgs e)
{
if(EventSeven!=null)
EventSeven(new object(),e);
}
piece of code, then the answer is:
一段代码,那么答案是:
If nobody subscribes to the "EventSeven" event handler then you'll get a null-reference exception on "EventSeven(new object(),e);"
如果没有人订阅“EventSeven”事件处理程序,那么您将在“EventSeven(new object(),e);”上得到一个空引用异常。
And the rule:
和规则:
The subscriber is responsible for adding a handler (+=) and removing it (-=) when he doesn't want to receive the events any longer. Garbage collection goes by the default rules, if an object is no longer referenced it can be cleaned.
订阅者负责添加处理程序 (+=) 并在他不想再接收事件时删除它 (-=)。垃圾收集遵循默认规则,如果一个对象不再被引用,它可以被清除。
回答by Marc Gravell
The problem is that if nobody subscribes the the event, it is null. And you can't invoke against a null. Three approaches leap to mind:
问题是,如果没有人订阅该事件,则它为空。而且您不能针对空值进行调用。想到了三种方法:
- check for null (see below)
- add a "do nothing" handler:
public event EventHandler MyEvent = delegate {};
- use an extension method (see below)
- 检查空值(见下文)
- 添加一个“什么都不做”的处理程序:
public event EventHandler MyEvent = delegate {};
- 使用扩展方法(见下文)
When checking for null, to be thread-safe, you must in theorycapture the delegate reference first (in case it changes between the check and the invoke):
在检查 null 时,为了线程安全,理论上您必须首先捕获委托引用(以防它在检查和调用之间发生变化):
protected virtual void OnMyEvent() {
EventHandler handler = MyEvent;
if(handler != null) handler(this, EventArgs.Empty);
}
Extension methods have the unusual property that they are callable on null instances...
扩展方法有一个不寻常的属性,它们可以在空实例上调用......
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);
}
then you can call:
然后你可以打电话:
MyEvent.SafeInvoke(this);
and it is both null-safe (via the check) and thread-safe (by reading the reference once only).
它既是空安全的(通过检查)又是线程安全的(通过只读取一次引用)。
回答by Steven Jeuris
Using PostSharpit is possible to adjust the compiled assembly in a post-compilation step. This allows you to apply 'aspects' to the code, resolving cross-cutting concerns.
使用PostSharp可以在编译后步骤中调整已编译的程序集。这允许您将“方面”应用于代码,解决横切关注点。
Although the null checks or empty delegate initialization might be a very minor issue, I wrote an aspect which resolves it by adding an empty delegate to all events in an assembly.
尽管空检查或空委托初始化可能是一个非常小的问题,但我编写了一个方面,通过向程序集中的所有事件添加一个空委托来解决它。
It's usage is quite easy:
它的使用非常简单:
[assembly: InitializeEventHandlers( AttributeTargetTypes = "Main.*" )]
namespace Main
{
...
}
I discussed the aspect in detail on my blog. In case you have PostSharp, here is the aspect:
我在我的博客上详细讨论了这个方面。如果你有 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 );
}
}
... and the helper method it uses:
...以及它使用的辅助方法:
/// <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 );
}
回答by unknown6656
I want to append some short information about the C# 6.0-Syntax:
我想附加一些关于 C# 6.0 语法的简短信息:
It is now possible to replace this:
现在可以替换这个:
var handler = EventSeven;
if (handler != null)
handler.Invoke(this, EventArgs.Empty);
with this:
有了这个:
handler?.Invoke(this, EventArgs.Empty);
结合它 expression-bodied membersexpression-bodied members,可以缩短以下代码:
protected virtual void OnMyEvent()
{
EventHandler handler = MyEvent;
handler?.Invoke(this, EventArgs.Empty);
}
down to a one-liner:
归结为单行:
protected virtual void OnMyEvent() => MyEvent?.Invoke(this, EventArgs.Empty);
See MSDN for more information about the null-conditional operator请参阅MSDN有关空条件运算符的更多信息
请参阅this blog这个博客关于表达身体的成员