这两行是否相同,'?……:'vs'??'?


80

这两行之间有区别吗?

MyName = (s.MyName == null) ? string.Empty : s.MyName

要么

MyName = s.MyName ?? string.Empty

8
检查它们的IL,这样一来,您将更好地了解它们是否会产生相同的代码,或者它们的处理方式有所不同
manman 2014年

12
@manman如果有人不理解高级代码,那么您会觉得IL代码更具可读性吗?
Servy 2014年

11
@Servy如果有些人对学习差异感到好奇,而其他人都在进行高层讨论,那么最好指出其他方式来理解差异,如果他很好奇,他可以去比较结果,不需要阅读整个IL
manman 2014年

6
唯一的区别是您评估s.MyName一次还是两次。
Tim S.

2
@Servy,因为您不必了解IL代码,只需要了解diff
wchargin 2014年

Answers:


166

更新:我写了一篇博客文章,更深入地讨论了这个主题。 http://www.codeducky.org/properties-fields-and-methods-oh-my/


通常,它们将返回相同的结果。但是,在少数情况下,当MyName是属性时,您会遇到明显的差异,因为MyName第一个示例中的getter将执行两次,而在第二个示例中将仅执行一次。

例如,MyName两次执行可能会导致性能差异:

string MyName
{
    get 
    {
        Thread.Sleep(10000);
        return "HELLO";
    }
}

否则,MyName如果MyName是有状态的,则两次执行可能会得到不同的结果:

private bool _MyNameHasBeenRead = false;

string MyName
{
    get 
    {
        if(_MyNameHasBeenRead)
                throw new Exception("Can't read MyName twice");
        _MyNameHasBeenRead = true;
        Thread.Sleep(10000);
        return "HELLO";
    }
}

或者,MyName如果MyName可以在不同的线程上进行更改,则两次执行可能会得到不同的结果:

void ChangeMyNameAsync()
{
    //MyName set to null in another thread which makes it 
    //possible for the first example to return null
    Task.Run(() => this.MyName = null);
}

string MyName { get; set; }  

这是实际代码的编译方式。首先用三元表达式表示:

IL_0007:  ldloc.0     // s
IL_0008:  callvirt    s.get_MyName       <-- first call
IL_000D:  brfalse.s   IL_0017
IL_000F:  ldloc.0     // s
IL_0010:  callvirt    s.get_MyName       <-- second call
IL_0015:  br.s        IL_001C
IL_0017:  ldsfld      System.String.Empty
IL_001C:  call        set_MyName

这是null运算符的组成部分:

IL_0007:  ldloc.0     // s
IL_0008:  callvirt    s.get_MyName       <-- only call
IL_000D:  dup         
IL_000E:  brtrue.s    IL_0016
IL_0010:  pop         
IL_0011:  ldsfld      System.String.Empty
IL_0016:  call        s.set_MyName

如您所见,三元运算符的编译代码将进行两次调用以获取属性值,而null-coalescing运算符将仅执行1。


3
MyName = s.MyName ?? string.Empty如果MyName是属性,则不会执行两次getter 。因此,如果您不想执行两次getter,则应使用第二行。
Steven Wexler

2
我随意添加了两段代码的IL示例。有关完整的LINQPad示例,请访问dropbox.com/s/x6zqdsjlkosxchf/SO21052437.linq
Lasse V. Karlsen

1
我认为您在IL代码的最后部分错过了“ s”字样;目前它的读数et_MyName应该是set_MyName
AJMansfield 2014年

2
另外,?:由于null上述原因,带有的长版本可能实际上会导致这种情况。如果"Value from other thread"上面实际上是一个null字符串,则可能会更具有说明性。然后,这将表明结果可能为空。如果有状态对象决定在访问器的第一次调用中返回一个字符串实例,而get仅在第二次调用时返回null ,则有状态对象也会发生同样的情况。
Jeppe Stig Nielsen

2
还要注意,C#4.0语言规范确保(在§7.14中)MyValue确实得到了两次评估,从而防止了抖动来优化该两次调用(就像我最初认为的那样)
BlackBear 2014年

26

如果该属性不仅仅是一个简单的getter,则在第一个非null情况下,您可能会执行两次函数。

如果该属性在有状态对象中,则对该属性的第二次调用可能返回不同的结果:

class MyClass
{
    private IEnumerator<string> _next = Next();

    public MyClass()
    {
        this._next.MoveNext();
    }

    public string MyName
    {
        get
        {
            var n = this._next.Current;
            this._next.MoveNext();
            return n;
        }
    }


    public static IEnumerator<string> Next()
    {
        yield return "foo";
        yield return "bar";
    }
}

同样,在非字符串情况下,该类可能重载==来执行与三元运算符不同的操作。我不认为三元运算符可以重载。


三元运算符绝对不能重载。即使C ++几乎可以让其他所有运算符重载,也无法重载?:。我还没有看过任何语言。
Darrel Hoffman

@DarrelHoffman总有Scala,您可以在其中滚动自己的位置;)
jdphenix 2014年

9

唯一的区别是您评估s.MyName两次还是一次。在s.MyName不为null的情况下,第一个将执行两次,第二个将只执行一次。

在大多数情况下,这种区别并不重要,我将继续讲第二种,因为它更加清晰简洁。


5

是的,两者相同,并且它是null-coalescing运算符

如果操作数不为null,则返回左侧的操作数;否则返回0。否则返回右操作数。

如果我们谈论效率,那么

string MyName = (s.MyName == null) ? string.Empty : s.MyName;
string MyName2 = s.MyName ?? string.Empty;

如果我使用反汇编程序,则可以看到第一条语句需要19条语句才能由编译器执行,而第二条语句仅需要12条语句即可执行。


感谢您提到这个名字。我想看看Java是否也有这个?操作员。事实并非如此,因此我将同时使用三元运算符。
developerwjk


0

他们完成相同的任务。

唯一的区别在于您的同事或正在阅读代码的人是否理解语法。

编辑:另外,第一个选项可以评估属性MyName两次。


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.