这两个陈述句是否相同?

6

以下两个代码片段是否实现了相同的功能?

我的原始代码:

if (safeFileNames != null)
{
    this.SafeFileNames = Convert.ToBoolean(safeFileNames.Value);
}
else
{
    this.SafeFileNames = false;
}

ReSharper认为更好的做法是:

this.SafeFileNames = safeFileNames != null && 
                     Convert.ToBoolean(safeFileNames.Value);

我认为上面的代码更易于阅读,是否有令人信服的理由要更改它?
它会执行得更快,最重要的是,代码是否会做完全相同的事情?

另外,如果您查看:Convert.ToBoolean(safeFileNames.Value); 部分,那么这肯定会导致空引用异常吗?

this.SafeFileNames = bool

本地的 safeFileNames 是一个强类型的自定义对象,以下是该类:

public class Configuration
    {
        public string Name
        {
            get;
            set;
        }
        public string Value
        {
            get;
            set;
        }
    }

3
由于语句的第一部分safeFileNames != null将会进行短路运算,因此您将不会遇到Convert.ToBoolean(safeFileNames.Value)所引发的NullReferenceException异常 -- 这就是&&运算符的工作方式。 - Nate
2
由于C#的惰性求值,它不会导致空引用异常。&&语句总是先评估左侧再评估右侧。但如果左侧为false,则不会考虑评估右侧,因为逻辑结果已经确定。因此,如果safeFileNames为空,则永远不会进行Convert.ToBoolean调用。这种情况在代码中非常常见。 - Nick Moore
1
所以如果我理解&& - 如果左侧为false,则返回false...否则它会评估右侧,并返回右侧的结果? - JL.
1
这完全正确。(同样相反的事情也会发生在||中。) - Nick Moore
1
@JL:是的,没错。这被称为短路求值。http://en.wikipedia.org/wiki/Short-circuit_evaluation - Randolpho
8个回答

24

你提出这个问题的事实表明对我来说第一个选项更受欢迎。也就是说,我认为你的问题暗示着你认为第一段代码更易于理解,并且你不确定第二段代码是否等效。软件设计的主要目标是管理复杂性。如果现在让你感到困惑,以后可能也会让你感到困惑,或者给之后维护你的代码的人带来困惑。


我完全同意,但我打算进行更改。我认为这是每个 .net 开发人员都应该知道的东西。所以我感到有点遗憾没有知道这个.. 但感谢大家提供的精彩课程... - JL.
这个回答在我的整个空值合并运算符帖子中被忽略了,对此我深感抱歉。非常好的答案! - Randolpho
确实是一个很好的答案,但我建议你选择ReSharper的方法,因为你现在应该已经理解他的方法了,并且它看起来可能更快(考虑到短路支持)。 - Hardryv
没错。请考虑,在发布前的星期五晚上10点修复你的代码的可怜人可能是在几个月后的你。同时,在代码中留下足够的线索(注释),以便重构你对程序逻辑的思考。 - David R Tribble

5
这里提供两份代码的IL代码。我使用了你的代码并创建了一个控制台应用程序以查看它们的IL代码。从生成的IL代码可以看出,其中一种方法(method2)比另一种方法短4个字节,但是它们运行的IL代码基本相同,因此就性能而言...不必担心。它们的性能都是相同的。更多关注哪个更易于阅读,更好地展示您的意图。

我的代码:

class Program
{
    static void Main(string[] args)
    {


    }
    public void method1()
    {
        bool? safeFileNames = null;

        if (safeFileNames != null)
        {
            SafeFileNames = Convert.ToBoolean(safeFileNames.Value);
        }
        else
        {
            SafeFileNames = false;
        }
    }
    public void method2()
    {
        bool? safeFileNames = null;
        SafeFileNames = safeFileNames != null && Convert.ToBoolean(safeFileNames.Value);
    }
    public static bool SafeFileNames { get; set; }
}

方法1的中间语言(IL)为:
.method public hidebysig instance void  method1() cil managed
{
  // Code size       42 (0x2a)
  .maxstack  1
  .locals init ([0] valuetype [mscorlib]System.Nullable`1<bool> safeFileNames)
  IL_0000:  ldloca.s   safeFileNames
  IL_0002:  initobj    valuetype [mscorlib]System.Nullable`1<bool>
  IL_0008:  ldloca.s   safeFileNames
  IL_000a:  call       instance bool valuetype [mscorlib]System.Nullable`1<bool>::get_HasValue()
  IL_000f:  brfalse.s  IL_0023
  IL_0011:  ldloca.s   safeFileNames
  IL_0013:  call       instance !0 valuetype [mscorlib]System.Nullable`1<bool>::get_Value()
  IL_0018:  call       bool [mscorlib]System.Convert::ToBoolean(bool)
  IL_001d:  call       void ConsoleApplication5.Program::set_SafeFileNames(bool)
  IL_0022:  ret
  IL_0023:  ldc.i4.0
  IL_0024:  call       void ConsoleApplication5.Program::set_SafeFileNames(bool)
  IL_0029:  ret
} // end of method Program::method1

方法2的中间语言(IL):
.method public hidebysig instance void  method2() cil managed
{
  // Code size       38 (0x26)
  .maxstack  1
  .locals init ([0] valuetype [mscorlib]System.Nullable`1<bool> safeFileNames)
  IL_0000:  ldloca.s   safeFileNames
  IL_0002:  initobj    valuetype [mscorlib]System.Nullable`1<bool>
  IL_0008:  ldloca.s   safeFileNames
  IL_000a:  call       instance bool valuetype [mscorlib]System.Nullable`1<bool>::get_HasValue()
  IL_000f:  brfalse.s  IL_001f
  IL_0011:  ldloca.s   safeFileNames
  IL_0013:  call       instance !0 valuetype [mscorlib]System.Nullable`1<bool>::get_Value()
  IL_0018:  call       bool [mscorlib]System.Convert::ToBoolean(bool)
  IL_001d:  br.s       IL_0020
  IL_001f:  ldc.i4.0
  IL_0020:  call       void ConsoleApplication5.Program::set_SafeFileNames(bool)
  IL_0025:  ret
} // end of method Program::method2

1
如果我可以给予超过1分的评价,我会这么做。我喜欢当人们检查IL以确定实际差异。 - Brett Allen
似乎是一个很好的借口来清理一下ildasm并玩一会儿 :-) - jvilalta

5

这两个语句实现的功能是完全相同的。使用哪个取决于个人偏好,但我更喜欢Resharper的版本。它更为简洁,组成部分更少。代码意图更容易理解。


如果 this.SafeFileNames 为空怎么办?尝试在null上执行 Convert.ToBoolean 会导致 null 引用异常吧? - JL.
@JL:如果SafeFileNames为空,第二个子句将永远不会执行。 - Randolpho
短路求值大获全胜。 - J. Steen

2

通过使用resharper的建议,它可以减少代码的圈复杂度。

是否更易读是个人观点问题,但我更喜欢resharper给出的建议。

它们是相同的,如果您想要更好的可读性,我也可以建议以下内容:

if (safeFileNames != null)
    this.SafeFileNames = Convert.ToBoolean(safeFileNames.Value);
else
    this.SafeFileNames = false;

或者

this.SafeFileNames = safeFileNames != null ? Convert.ToBoolean(safeFileNames.Value) : false

注意:当大括号不必要时,将它们删除可以使代码看起来更好,这又是非常主观的。 - Brett Allen
@Aequtarium,你的回答并不是不正确或低效的。只是可能没有其他回答那么令人兴奋吧。我投了赞成票。 - Robert Harvey
这只是一种观点,但值得注意的是,StyleCop会将其标记为错误。一致性是一个因素;另一个因素是在if语句中添加第二个语句(没有else),并忘记同时添加大括号时引入错误。IDE通常会缩进并通知您,但如果您恰好使用Visual Notepad... - Dave Mateer

1

是的,这两个语句都会做同样的事情,你不必把ReSharper的建议当作圣经,一个人认为可读的代码在另一个人看来可能是一团糟。

还有其他几种方法可以实现你想要做的事情,可能更易读。safeFileNames是什么值类型?它看起来可能是一个可空的bool?如果是这样,你可以简单地写:

this.SafeFileNames = safeFileNames.GetValueOrDefault();

1

它们是相同的。&& 是一个短路运算符,因此如果 safeFileNames 为 null,则表达式的后半部分不会被评估。


1

它们是相同的。在一行代码中,如果第一个条件失败,则不会评估第二个条件。因此,您不会得到空引用。

我打赌两种情况下的IL都是相同的。

我更喜欢第二个版本。


0

从逻辑上讲,它们是相同的。任何性能差异可能都是微不足道的。由于第二种形式消除了条件语句,因此在某些平台上,第二种形式可能会转换为更有效的二进制代码。条件语句(不正确的推测执行)可能会在 CPU 密集型工作中破坏 CPU 的指令流水线。但是,无论是 IL 还是 JITter 都需要发出足够质量的代码,才能使这种差异产生很大影响。

我同意您对可读性的看法,但我并不认为每个人都有相同的看法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接