这两条线是否相同,'?......:'vs'??'?

use*_*149 80 c#

这两条线之间有区别吗?

MyName = (s.MyName == null) ? string.Empty : s.MyName
Run Code Online (Sandbox Code Playgroud)

要么

MyName = s.MyName ?? string.Empty
Run Code Online (Sandbox Code Playgroud)

Ste*_*ler 166

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


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

例如,您可能会遇到执行MyName两次性能差异:

string MyName
{
    get 
    {
        Thread.Sleep(10000);
        return "HELLO";
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,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";
    }
}
Run Code Online (Sandbox Code Playgroud)

或者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; }  
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

这是使用null-coalescing运算符的部分:

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
Run Code Online (Sandbox Code Playgroud)

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

  • `MyName = s.MyName ?? 如果`MyName`是属性,string.Empty`将不会执行两次getter.因此,如果您不想两次执行getter,那么您应该使用第二行. (3认同)
  • 我冒昧地添加了两段代码的IL示例.有关完整的[LINQPad](http://linqpad.net)示例,请查看https://www.dropbox.com/s/x6zqdsjlkosxchf/SO21052437.linq (2认同)
  • 此外,由于上述原因,带有"?:"的长版本实际上可能会导致"null".如果上面的"其他线程的值"实际上是一个"null"字符串,那么它可能更具说明性.然后,这将显示结果**可以**为空.如果对象决定在第一次调用`get`addoor时返回一个字符串实例并且仅在第二次调用时返回null,那么有状态对象也会发生同样的情况. (2认同)
  • 另请注意,C#4.0语言规范确保(在§7.14中)MyValue确实被评估了两次,从而防止抖动优化掉那个双重调用(正如我原先认为的那样) (2认同)

Dan*_*her 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";
    }
}
Run Code Online (Sandbox Code Playgroud)

此外,在非字符串的情况下,类可能会重载==以执行与三元运算符不同的操作.我不相信三元运算符可能会过载.


Tim*_* S. 9

唯一的区别是你评估s.MyName两次还是一次.第一个将在s.MyName非空的情况下执行两次,第二个将只评估一次.

在大多数情况下,这种差异并不重要,我会选择第二种,因为它更清晰简洁.


Sac*_*hin 5

是的,两者都是相同的,它是null-coalescing运算符.

如果操作数不为null,则返回左侧操作数; 否则它返回右手操作数.

如果我们谈论效率那么

string MyName = (s.MyName == null) ? string.Empty : s.MyName;
string MyName2 = s.MyName ?? string.Empty;
Run Code Online (Sandbox Code Playgroud)

如果我使用解析器,那么我可以看到第一个语句需要19个语句由编译器执行,而第二个语句只需要执行12个语句.