检查字符串是否为null或空字符串的顺序是否重要?

9

为了我的编程考试,我不得不为我写的代码进行辩护。其中一行是:

if(app.Logourl == "" || app.Logourl == null)

他问我空值和空字符串之间是否有区别。我告诉他,区别在于 null 表示没有指向任何东西,因此未实例化,而空字符串则不同。
考试后,我走到他跟前问他是否正确,因为我看到他脸上露出了奇怪的表情。他告诉我这是真的,但我检查值的顺序是错误的。
现在几天过去了,我相信顺序没有问题。我正确吗? 简短概述:
if(app.Logourl == "" || app.Logourl == null)

等同于

if(app.Logourl == null || app.Logourl == "")

4
请使用 String.IsNullOrEmpty 代替。;) - Farhad Jabiyev
1
Farhad是正确的,当然。除此之外,对于微小的优化,将您认为最常为真的条件放在第一位,以便短路评估在更多情况下跳过最后一个检查。 - Jeppe Stig Nielsen
1
正如其他人指出的那样,在您的实际情况中都是一样的。然而,我建议按照您的老师建议的路线,并习惯始终首先检查null。正如答案中所指出的,在许多实际情况下,这确实很重要,并且从null检查开始将简单地减少您在长期内的错误。无论何时环境提供了像IsNullOrEmpty()这样的专门函数,请使用它,但在许多其他情况下,您也需要手动编写检查的代码。 - Gábor
5个回答

14

你的方式是可以的,因为对于System.String类型的==运算符重载会调用String.Equals方法,该方法允许传入null参数。

不过这并非具有普适性的。如果你想检查字符串长度而不是使用== "",你的第一个代码片段就会有问题:

if(app.Logourl.Length == 0 || app.Logourl == null) // <<== Wrong!

而第二个选项则是可行的:

if(app.Logourl == null || app.Logourl.Length == 0) // <<== Correct
这是因为在评估 ||&& 运算符时发生了短路:一旦它们知道结果 (true 对于 ||false 对于 &&),它们就停止评估。在上面的第二个片段中,如果 app.Logourlnull,则表达式的第二部分将被忽略,因此 app.Logourl.Length 不会引发空引用异常。 注意: 为了方便进行这种检查,C# 类库提供了一个检查方法
if (string.IsNullOrEmpty(app.Logourl)) {
    ...
}

2
对于你的第一个陈述,我可以补充一下,C#规范定义的==运算符的所有重载,包括可空类型上的提升运算符以及BCL定义的所有额外重载,都允许(即不会抛出异常)其中一个或两个操作数为null。用户可以引入一个抛出异常的==重载,但我想那将违反==实现的“契约”。 - Jeppe Stig Nielsen

2
    private static bool IsNullOrEmpty(string s)
    {
        return s == null || s == "";

        /*
         Lets look behind the scenes here:
         =================================
        IL_0000: ldarg.0   => load s on the evaluation stack   
        IL_0001: brfalse.s IL_000f => GoTo label 'IL_000f' if loaded argument is null

        IL_0003: ldarg.0    => load s on the evaluation stack  
        IL_0004: ldstr ""   => load constant string "" to the evaluation stack
        IL_0009: call bool [mscorlib]System.String::op_Equality(string, string)
                              => Call String.Equality(string,string) with s and "" 
                                 loaded to the evalutation stack
                                 that will pop the two values compare them for equality and load the result.
                                 to the evaluation stack.                                     

        IL_000e: ret  => Return to the caller with equlity result on the evauation stack.

        IL_000f: ldc.i4.1 => Load constant value 1(4 byte which will represent "True") to the evaluation stack
                             and return to the caller.In our flow it's the case when s is null.
        IL_0010: ret 

         In Summary:
         ===========
        1.) IL instructions total code size 17 bytes.
        2.) Best case scenario execution path => 2 IL instructions.
        3.) Worst case scenario execution pat => 8 IL instructions.

        */
    }

    private static bool IsEmptyOrNull(string s)
    {
        return s == "" || s == null;

        /*
         Lets look behind the scenes here:
         =================================
         IL_0000: ldarg.0  => load s on the evaluation stack   
         IL_0001: ldstr "" => load constant string "" to the evaluation stack
         IL_0006: call bool [mscorlib]System.String::op_Equality(string, string)
         IL_000b: brtrue.s IL_0012

         IL_000d: ldarg.0 => load s on the evaluation stack   
         IL_000e: ldnull  => load constant null on the evaluation stack 
         IL_000f: ceq => Pop two loaded values compare and push the result back on the evaluation stack
         IL_0011: ret

         IL_0012: ldc.i4.1 => Load constant value 1(4 byte which will represent "True") to the evaluation stack
                             and return to the caller.In our flow it's the case when s is null.
         IL_0013: ret 

          In Summary:
         ===========
        1.) IL instructions total code size 20 bytes.
        2.) Best case scenario execution path => 6 IL instructions.
        3.) Worst case scenario execution path => 10 IL instructions.
     */

    }

结论:

仅从IL生成的代码判断,“if(app.Logourl == "" || app.Logourl == null)”是“微观优化”,在性能方面更好:)


1

我是一个评论。

在通常情况下,这并不重要。但是可能会有副作用。下面是一个简单的例子,可以从中学习:

static class Program
{
    static string Logourl
    {
        get
        {
            Console.WriteLine("getter runs");
            return null;
        }
    }

    static void Main()
    {
        if (Logourl == "" || Logourl == null)
        {
        }
    }
}

该程序将输出:
获取器运行
获取器运行
如果您交换检查的顺序,则getter runs只会打印一次。如果将属性更改为return "";,则情况将相反。
当然,使用string.IsNullOrEmpty(Logurl)将始终恰好检索一次属性。

1

不,这在你的情况下并不重要。

但需要知道的一件事是,布尔运算符 &&|| 会短路,也就是说如果你有 a || b 并且 atrue,那么 b 不会被评估。

例如,

app.Logourl == null || app.Logourl == ""

如果app.Logourlnull,那么app.Logourl == ""甚至都不会被评估。
在您的情况下,检查一个或另一个没有实质性区别。先检查哪个或先检查另一个都可以。如果这些检查不同,则可能很重要。
例如,
app.Logourl == null || app.Logourl.Equals("")

如果您按照另一种顺序进行,如果app.Logourl为空,您将会收到一个异常,因为您不能调用空引用的成员函数。

我会使用 String.IsNullOrEmpty(app.Logourl),这是标准库。


0

当我确定我的对象是一个字符串时,我总是更喜欢以下方式:

    if (string.IsNullOrEmpty(yourString))
    {
        // this string is null or empty
    }

或者这样:

    if (string.IsNullOrWhiteSpace(yourString))
    {
        // this string is null or empty (or got only a space)
    }

1
请注意,如果您不确定它是否为字符串,即如果声明的类型(编译时类型)是objectstring实现的接口,则说**obj == ""**应该会给出编译时警告。这很危险,因为它检查引用相等性。有时会有多个空字符串的实例。 - Jeppe Stig Nielsen

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