【问题标题】:Check if null before set to null?在设置为null之前检​​查是否为null?
【发布时间】:2017-02-23 13:21:19
【问题描述】:

我们应该在将变量设置为null之前检​​查它是否为null吗?

if (MyBills != null) 
{
    MyBills = null;
}

例如,在Java related question 中,性能影响很小。 这是C#中的情况吗?其他影响?

测试

我创建了以下代码进行测试:

var watch = System.Diagnostics.Stopwatch.StartNew();

int iterations = int.MaxValue;
List<int> myBills= null;
for (var i = 0; i < iterations; i++)
{
    if (myBills!= null)
    {
        myBills = null;
    }
}
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
Console.WriteLine(elapsedMs);

在有和没有if (myList != null)rextester 上运行它,结果如下:

With check      Without check
988             941
938             1021
953             987
998             973
1004            1031

Average 
976.2           990.6

因此,即使在非受控环境中对其进行测试,性能影响也无关紧要。

【问题讨论】:

  • 当然不,为什么?
  • 你所谓的variable可能是一个带有setter的属性,你不想运行哪些代码,那么你就这样做。
  • “我们应该检查...” 仅当您想处理这种情况时,f.e.记录它
  • @MegaTron,确切地说。这就是问题所在。我会很感激downvoters cmets。
  • @MarioLevrero 在您完成您的测试后,我用我的测试编辑了我的答案,这很好!我想在此基础上可能值得保留它。

标签: c# .net null


【解决方案1】:

不,没有多大用处。可能检查 变量 是否为 null 与将其多次设置为 null 一样昂贵。

如果它是一个属性,它背后有额外的逻辑,之前测试它可能是有意义的,但这实际上应该是属性中逻辑的责任,而不是你的代码。

【讨论】:

  • 谢谢,帕特里克。我不明白为什么这个问题有这么多反对者。
  • 也许他们希望在询问时付出更多的努力,就像现在您链接到 Java 作为比较。您也可以进行一些测试。
  • Patrick,我已经按照你的建议用一些测试更新了这个问题。我认为只有像你这样有建设性的批评者,我们才能做出更好的 S.O.再次感谢
  • 不客气。确实,它并不像您在测试中看到的那样在意。虽然有更好的测试方法,但会产生更准确的结果。
【解决方案2】:

除了属性/设置器参数之外,我在您提供的代码中看不到任何逻辑原因。

但是,如果您想在将对象设置为 null 之前对它执行额外的操作,这是有道理的:

if (MyBills != null)
{
     MyBills.Dispose();
     MyBills = null;
}

或执行一些其他操作,例如记录结果。

但这两个示例都在您提供的示例代码之外。


因为人们似乎在质疑我的第一个示例的用例,所以这里有一个。这将防止多次调用Dispose 导致ObjectDisposedException

public class MyClass : IDisposable
{
    private SomeDisposableObject _disposableObject;

    //Some code

    public void Dispose()
    {
        if (_disposableObject != null)
        {
            _disposableObject.Dispose();
            _disposableObject = null;
        }
    }
}

我做了一些计时,发现如下结果:

带空检查的值类 = 1361
带空检查的 nullClass = 1173
没有空值检查的 valueClass = 1208
没有空检查的 nullClass = 1148

正如您所见,如果没有 null 检查,它会稍微快一些,但不足以进行任何重大优化。此外,我在调试模式下从编译器执行了这些计时,并关闭了优化,因此它们不是 100% 准确/相关。

但是,当我在发布模式下运行时,在编译器之外启用了优化,结果非常接近,以至于检查与否变得更加微不足道。

以及测试代码:

using System;

namespace NullCheckTest
{
    class Program
    {
        const int iterations = 10000000;

        static void Main(string[] args)
        {
            MyClass valueClass = new MyClass() { Value = 10 };
            MyClass nullClass = null;

            Console.WriteLine($"valueClass with null check = {TestNullCheck(valueClass)}");
            Console.WriteLine($"nullClass with null check = {TestNullCheck(nullClass)}");

            Console.WriteLine($"valueClass with no null check = {TestNoNullCheck(valueClass)}");
            Console.WriteLine($"nullClass with no null check = {TestNoNullCheck(nullClass)}");

            Console.ReadLine();
        }

        static long TestNullCheck(MyClass myClass)
        {
            MyClass initial = myClass;

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            for (int i = 0; i < iterations; ++i)
            {
                sw.Start();

                if (myClass != null)
                {
                    myClass = null;
                }

                sw.Stop();

                myClass = initial;
            }

            return sw.ElapsedMilliseconds;
        }

        static long TestNoNullCheck(MyClass myClass)
        {
            MyClass initial = myClass;

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            for (int i = 0; i < iterations; ++i)
            {
                sw.Start();

                myClass = null;

                sw.Stop();

                myClass = initial;
            }

            return sw.ElapsedMilliseconds;
        }
    }

    public class MyClass
    {
        public int Value { get; set; }
    }
}

【讨论】:

  • 嗯,你正在修改 OP 的代码,在里面添加一些东西。这并不是他问题的真正答案。
  • @Sinatr 这就是我的结束声明所说的。但是,它仍然是一个答案,因为它说明了一个用例。
  • 手动拨打Dispose的情况并不多。 using 对此更有用。
  • @PatrickHofman 除了在具有需要处理的字段的一次性类中。在我看来,这是一个常见的用例。
  • 如果一个类支持IDisposable,我会在调用时用using块包裹它。无需设置为空。此外,如果它是一个类的实例,只需再次创建新实例。喜欢var mc = new MyClass();
【解决方案3】:

回答这个问题:不,没有必要。

但是。作为一般经验法则,对象变量和属性永远不应该为空,因为如果值被设置在某处,它会迫使您始终检查代码。

您应该做的是使用自定义类Maybe&lt;&gt; 来指示对象变量或属性可能未初始化。

像这样:

public class Maybe<T> where T : class
{
    public readonly T Value { get; }

    public Maybe(T value)
    {
        _Value = value;
    }

    public bool HasValue => _Value != null;
    public bool HasNoValue => _Value == null;

    public static operation Maybe<T>(T value) => new Maybe(value);
}

那么你可以这样使用它:

public class Foo
{
    public Maybe<Bar> Bar { get; set; }
    public int GetBarCount()
    {
        return Bar.HasValue ? Bar.Count : 0;
    }
}

public class Bar
{
    public int Count { get; set; }
}

以上代码适用于以下所有示例:

Foo foo = new Foo();
foo.Bar = new Bar(); // Outputs 0 when GetBarCount is called
foo.Bar = new Bar { Count = 2 }; // Outputs 2 when GetBarCount is called
foo.Bar = new Maybe<Bar>(new Bar()); // Outputs 0 when GetBarCount is called
foo.Bar = new Maybe<Bar>(new Bar { Count = 2 }); // Outputs 2 when GetBarCount is called
foo.Bar = new Maybe<Bar>(null); // Outputs 0 when GetBarCount is called
foo.Bar = null;  // Outputs 0 when GetBarCount is called

注意! - 使用Maybe&lt;Bar&gt; 时,您明确地将Foo.Bar 标记为并不总是具有价值,并提供了强烈的信号。但是,不使用Maybe&lt;Bar&gt; 标记它,表明它总是有一个值。通过在正确的位置使用 Maybe&lt;&gt; 包装器,代码变得更易于使用,因为您不再需要对未包装的对象变量和属性进行 null 检查。

【讨论】:

  • 为什么要这样混淆代码?此外,您仍在检查 null,在我看来,这个包装类令人困惑且毫无意义。
  • 查看我在底部添加的注释。它应该解释得很好。
  • 我明白你为什么这样做,我只是不明白这样做的意义。
  • return Bar.HasValue ? Bar.Count : 0;return Bar?.Count ?? 0; 有什么用?这会产生巨大的开销,而根本没有任何收益。
  • 呸。呸,呸,呸。
猜你喜欢
  • 2010-10-09
  • 1970-01-01
  • 1970-01-01
  • 2021-03-23
  • 1970-01-01
  • 2021-01-08
  • 1970-01-01
  • 2014-03-24
  • 2014-07-16
相关资源
最近更新 更多