C# 在分配相同的var时,双问号(';??';)与if

C# 在分配相同的var时,双问号(';??';)与if,c#,compiler-optimization,null-coalescing-operator,C#,Compiler Optimization,Null Coalescing Operator,参考以下内容 写作时 A = A ?? B; 这和 if( null != A ) A = A; else A = B; 这是不是意味着 if( null == A ) A = B; 最好是性能方面的 或者我可以假设当同一个对象在?符号中时,编译器会优化代码吗?不要担心性能,它可以忽略不计 如果您对此感到好奇,请编写一些代码,使用Stopwatch测试性能,然后查看。不过,我怀疑您需要进行几百万次迭代才能开始看到差异 你也永远不能对事情的实现进行假设,它们在未来很可能会发生

参考以下内容

写作时

A = A ?? B;
这和

if( null != A )
    A = A;
else
    A = B;
这是不是意味着

if( null == A ) A = B;
最好是性能方面的


或者我可以假设当同一个对象在
符号中时,编译器会优化代码吗?

不要担心性能,它可以忽略不计

如果您对此感到好奇,请编写一些代码,使用
Stopwatch
测试性能,然后查看。不过,我怀疑您需要进行几百万次迭代才能开始看到差异

你也永远不能对事情的实现进行假设,它们在未来很可能会发生变化——使你的假设无效

我的假设是,性能差异可能非常非常小。我个人倾向于使用空合并操作符以提高可读性,它很好,可以很好地压缩和表达这一点。我有时这样做是为了延迟负载检查:

_lazyItem = _lazyItem ?? new LazyItem();

我的建议是检查IL(中间语言)并比较不同的结果。然后,您可以确切地看到每种方法的最终结果,并确定哪种方法更优化。但是正如Adam在他的评论中所说的,您最有可能关注的是可读性/可维护性,而不是这么小的东西的性能

编辑:您可以使用visual studio附带的ILDASM.exe查看IL并打开已编译的程序集。

我刚刚在C#中很快地尝试了这个方法,因此我的方法中可能有错误。我使用了以下代码,并确定第二种方法比第一种方法花费的时间长约1.75倍。
@Lockszmith:在下面的编辑之后,比率为1.115,有利于第一次实施

即使他们花了同样的时间,我个人也会使用内置的语言构造,因为它可以更清楚地向任何可能有更多内置优化的未来编译器表达您的意图

@Lockszmith:我编辑了代码以反映评论中的建议

var A = new object();
var B = new object();

var iterations = 1000000000;

var sw = new Stopwatch();
for (int i = 0; i < iterations; i++)
{   
    if( i == 1 ) sw.Start();
    if (A == null)
    {
        A = B;
    }
}
sw.Stop();
var first = sw.Elapsed;

sw.Reset();
for (int i = 0; i < iterations; i++)
{
    if( i == 1 ) sw.Start();
    A = A ?? B;
}
sw.Stop();
var second = sw.Elapsed;

first.Dump();
second.Dump();

(first.TotalMilliseconds / second.TotalMilliseconds).Dump("Ratio");
var A=新对象();
var B=新对象();
var迭代次数=100000000;
var sw=新秒表();
对于(int i=0;i
虽然
??
的性能可以忽略不计,但副作用有时可能无法忽略。考虑下面的程序。

using System;
using System.Diagnostics;
using System.Threading;

namespace TestProject
{
    class Program
    {
        private string str = "xxxxxxxxxxxxxxx";
        public string Str
        {
            get
            {
                return str;
            }
            set
            {
                if (str != value)
                {
                    str = value;
                }
                // Do some work which take 1 second
                Thread.Sleep(1000);
            }
        }

        static void Main(string[] args)
        {
            var p = new Program();

            var iterations = 10;

            var sw = new Stopwatch();
            for (int i = 0; i < iterations; i++)
            {
                if (i == 1) sw.Start();
                if (p.Str == null)
                {
                    p.Str = "yyyy";
                }
            }
            sw.Stop();
            var first = sw.Elapsed;

            sw.Reset();
            for (int i = 0; i < iterations; i++)
            {
                if (i == 1) sw.Start();
                p.Str = p.Str ?? "yyyy";
            }
            sw.Stop();
            var second = sw.Elapsed;

            Console.WriteLine(first);
            Console.WriteLine(second);

            Console.Write("Ratio: ");
            Console.WriteLine(second.TotalMilliseconds / first.TotalMilliseconds);
            Console.ReadLine();
        }

    }
}
因为有一个额外的赋值使用
,而赋值的性能有时可能无法保证。这可能会导致性能问题


我宁愿使用
if(null==A)A=B而不是
A=A??B

是的,有区别

使用Visual Studio 2017 15.9.8
瞄准
.NET Framework 4.6.1
。考虑下面的样本。

static void Main(string[] args)
{
    // Make sure our null value is not optimized away by the compiler!
    var s = args.Length > 100 ? args[100] : null;
    var foo = string.Empty;
    var bar = string.Empty;

    foo = s ?? "foo";
    bar = s != null ? s : "baz";

    // Do not optimize away our stuff above!
    Console.WriteLine($"{foo} {bar}");
}
使用
ILDasm
可以清楚地看出,编译器并没有平等地对待这些语句

??操作员

IL_001c:  dup
IL_001d:  brtrue.s   IL_0025
IL_001f:  pop
IL_0020:  ldstr      "foo"
IL_0025:  ldloc.0
IL_0026:  brtrue.s   IL_002f
IL_0028:  ldstr      "baz"
IL_002d:  br.s       IL_0030
IL_002f:  ldloc.0
条件空检查

IL_001c:  dup
IL_001d:  brtrue.s   IL_0025
IL_001f:  pop
IL_0020:  ldstr      "foo"
IL_0025:  ldloc.0
IL_0026:  brtrue.s   IL_002f
IL_0028:  ldstr      "baz"
IL_002d:  br.s       IL_0030
IL_002f:  ldloc.0
显然,
运算符意味着堆栈值的重复(应该是
s
变量对吗?)。我运行了一个简单的测试(多次),以了解两者中哪一个更快。在
string
上运行,在这台特定的机器上运行,我得到了以下平均数:

?? operator took:           583 ms
null-check condition took: 1045 ms
基准测试示例代码:

static void Main(string[] args)
{
    const int loopCount = 1000000000;
    var s = args.Length > 1 ? args[1] : null; // Compiler knows 's' can be null
    int sum = 0;

    var watch = new System.Diagnostics.Stopwatch();
    watch.Start();

    for (int i = 0; i < loopCount; i++)
    {
        sum += (s ?? "o").Length;
    }

    watch.Stop();

    Console.WriteLine($"?? operator took {watch.ElapsedMilliseconds} ms");

    sum = 0;

    watch.Restart();

    for (int i = 0; i < loopCount; i++)
    {
        sum += (s != null ? s : "o").Length;
    }

    watch.Stop();

    Console.WriteLine($"null-check condition took {watch.ElapsedMilliseconds} ms");
}
static void Main(字符串[]args)
{
const int loopCount=100000000;
var s=args.Length>1?args[1]:null;//编译器知道“s”可以为null
整数和=0;
var-watch=新系统.Diagnostics.Stopwatch();
watch.Start();
for(int i=0;i
所以答案是肯定的,这是有区别的。


另外,StackOverflow应该在同一句话中自动警告提到“性能”和“可忽略不计”的帖子。只有原始海报才能确定时间单位是否可忽略。

是的,此处的性能指标不存在。编写两个版本,编译,使用ILDasm或dotPeek查看生成的代码是否有任何差异。减号表示可读性。检查null是否等于任何值都没有意义。可能只是首选项,但我总是使用if(A!=null)。您正在对执行检查,而不是null。条件的顺序主要是首选项。我发现将比较值放在左边更具可读性,因为它将焦点放在最终结果上。习惯是旧的,源于C和C++中的编码,其中“=”可以在比较时意外分配,在C中它不再相关,但我发现比较更可读。如果你对C/C++有兴趣,查看一下仍然有效的C++书籍(),也可以看到下面的讨论:我刚刚意识到这意味着内置语言的构建需要更长的时间。这是令人惊讶的。。。也许在代码上执行了一些其他的优化,使得测试不公平?不是真的,是吗??route每次都执行空检查和赋值,而
a==null
路由只执行空检查和不赋值。尝试一个测试,
a
每次都需要分配。另外,使用秒表比使用日期时间更准确。另外,你需要至少跑一次,以确保在进行计时跑之前,该批次已经过JIT