如果C#中的方法调用不为null


106

是否有可能以某种方式缩短此声明?

if (obj != null)
    obj.SomeMethod();

因为我碰巧写了很多,这很烦人。我唯一能想到的就是实现Null Object模式,但这并不是我每次都能做的事情,并且它当然不是缩短语法的解决方案。

以及类似的事件问题

public event Func<string> MyEvent;

然后调用

if (MyEvent != null)
    MyEvent.Invoke();

41
我们考虑在C#4中添加一个新的运算符:“ obj.?SomeMethod()”的意思是“如果obj不为null,则调用SomeMethod,否则返回null”。不幸的是,它不适合我们的预算,因此我们从未实施。
埃里克·利珀特

@Eric:此评论仍然有效吗?我在某处看到过,它在4.0中可用吗?
CharithJ

@CharithJ:不。它从未实现。
埃里克·利珀特

3
@CharithJ:我知道null合并运算符的存在。它没有达斯想要的;他想要一个可以撤消的成员访问运算符。(顺便说一句,您先前的评论对null合并运算符进行了错误的描述。您的意思是“ v = m == null?y:m.Value可以写为v = m ?? y”。)
Eric利珀特

4
对于较新的读者:C#6.0实现?。因此,如果x,y或z为null,则x?.y?.z?.ToString()将返回null;如果它们都不为null,则将返回z.ToString()。
大卫

Answers:


162

从C#6开始,您可以使用:

MyEvent?.Invoke();

要么:

obj?.SomeMethod();

?.是null传播算子,并且将导致.Invoke()要被短路时的操作数是null。操作数仅被访问一次,因此没有“检查和调用之间的值更改”问题的风险。

===

在C#6之前,否:除零例外,没有null安全魔术。扩展方法-例如:

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

现在这是有效的:

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

在事件的情况下,这样做的好处是还消除了竞争条件,即您不需要临时变量。因此,通常您需要:

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

但具有:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

我们可以简单地使用:

SomeEvent.SafeInvoke(this); // no race condition, no null risk

1
对此一点矛盾。对于动作或事件处理程序-通常更独立-这是有道理的。不过,我不会破坏常规方法的封装。这意味着在单独的静态类中创建方法,并且我认为丢失封装和降低可读性/代码组织总体上不值得对本地可读性进行较小的改进
tvanfosson,2009年

@tvanfosson-确实;但我的意思是,这是我知道它将在哪里工作的唯一情况。问题本身引发了代表/事件的话题。
Marc Gravell

该代码最终在某处生成一个匿名方法,这实际上弄乱了任何异常的堆栈跟踪。可以给匿名方法命名吗?;)
sisve

按照2015 / C#6.0错误...?是他吗?如果为null,则ReferenceTHatMayBeNull?.CallMethod()将不会调用该方法。
TomTom

1
@mercu它应该是?.-在VB14和上述
马克Gravell

27

什么你要找的是空条件(不是“合并”)运算符:?.。从C#6开始可用。

您的示例将是obj?.SomeMethod();。如果obj为null,则什么也不会发生。当该方法具有参数时,例如obj?.SomeMethod(new Foo(), GetBar());,如果参数obj为null,则不评估参数,这对评估参数是否会产生副作用至关重要。

链接是可能的: myObject?.Items?[0]?.DoSomething()


1
这是太棒了。值得注意的是,这是C#6的功能...(在VS2015语句中将隐含该功能,但仍然值得注意)。:)
凯尔·古德

10

快速扩展方法:

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

例:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

或者:

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

例:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));

试图用扩展方法来做,但最终得到几乎相同的代码。但是,此实现的问题在于它不适用于返回值类型的表达式。因此,必须有第二种方法。
orad 2014年

4

可以使用永远不会删除的空默认委托来初始化事件:

public event EventHandler MyEvent = delegate { };

无需进行空检查。

[ 更新,感谢Bevan指出了这一点]

但是,请注意可能的性能影响。我所做的快速微型基准测试表明,使用“默认委托”模式时,没有订阅者的事件处理速度要慢2-3倍。(在我的双核2.5GHz笔记本电脑上,这意味着279ms:785ms可以引发5000万个未订阅的事件。)对于应用程序热点,这可能是一个要考虑的问题。


1
因此,您可以通过调用一个空的委托来避免空检查……为了节省一些击键次数而在内存和时间上都做出了可衡量的牺牲?YMMV,但对我而言,这是一个不小的折衷。
Bevan

我还看到了一些基准测试,这些基准显示调用具有多个代表订阅的事件比调用一个事件要昂贵得多。
格雷格D


2

伊恩·格里菲斯(Ian Griffiths)的这篇文章为这个问题提供了两种不同的解决方案,他得出的结论是您不应该使用的巧妙技巧。


3
作为该文章的作者,我要补充一点,因为C#6使用空条件运算符(?。和。[])开箱即用地解决了这个问题,您绝对不应该使用它。
伊恩·格里菲思

2

像上面建议的那样,采用分块扩展方法并不能真正解决种族问题,而是将它们隐藏起来。

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

如前所述,此代码与带有临时变量的解决方案非常相似,但是...

两者的问题在于,在事件取消订阅之后,该事件的子订阅可能会被称为。之所以可能这样做,是因为在将委托实例复制到temp变量之后(或在上面的方法中作为参数传递)但在调用委托之前,可能会发生取消订阅的情况。

通常,在这种情况下,客户端代码的行为是不可预测的:组件状态已不允许处理事件通知。可以以处理客户端代码的方式编写客户端代码,但这会给客户端带来不必要的责任。

确保线程安全的唯一已知方法是对事件的发送者使用lock语句。这样可以确保所有订阅\取消订阅\调用被序列化。

为了更准确,应该将锁应用于在add \ remove事件访问器方法中使用的同一同步对象,默认情况下为'this'。


这不是所引用的竞争条件。竞争条件是if(MyEvent!= null)// MyEvent现在不为null MyEvent.Invoke(); // MyEvent现在为null,不好的事情会发生,所以在这种竞争条件下,我无法编写保证正常工作的异步事件处理程序。但是,根据您的“竞赛条件”,我可以编写一个保证能正常工作的异步事件处理程序。当然,对订户和取消订户的调用必须是同步的,或者在那里需要锁定代码。
jyoung,2009年

确实。我对此问题的分析发布在这里: blogs.msdn.com/ericlippert/archive/2009/04/29/…–
埃里克·利珀特


1

也许不是更好,但我认为更具可读性的是创建一个扩展方法

public static bool IsNull(this object obj) {
 return obj == null;
}

1
什么return obj == null意思 会是什么返回
哈马德·汗

1
这意味着,如果objnull该方法将返回true,我想。
2013年

1
这如何回答问题?
Kugel 2014年

回复较晚,但是您确定这可以解决吗?您可以在的对象上调用扩展方法null吗?我很确定这不适用于类型自己的方法,因此我怀疑它是否也可以与扩展方法一起使用。我相信检查对象是否 null存在的最佳方法是obj is null。不幸的是,检查一个对象是否不是null不需要用括号括起来,这是不幸的。
natiiix

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.