在C#中,比较字符串与null和""返回true的最佳方法是什么?

13
我有下面的代码(我试图检测字段的更改)
 if (person.State != source.State)
 {
      //update my data . .
  }

问题在于,有些情况下 person.State 是 NULL,而 source.State 是 "",因此返回 true。

如果其中一个为 null,另一个为空字符串,则我希望将它们视为相等,并且不更新我的数据。最干净的方法是什么?我需要创建自己的 Comparer 对象吗?因为这似乎是一个通用问题。


顺便问一下,Microsoft.VisualBasic 中是否有公共字符串比较例程来实现这个功能,因为这是 VB.NET 字符串的默认比较方式? - Mark Hurd
10个回答

23

如果你真的需要,你可以这样做:

if ((person.State ?? string.Empty) != (source.State ?? string.Empty))
{
    // ...
}

然而,根据您的需求,更好的解决方案可能是修改您的person.State属性,以便永远不返回null值。

public class Person
{
    string _state = string.Empty;
    public string State
    {
        get { return _state; }
        set { _state = value ?? string.Empty; }
    }
}

2
一个非常微小的观察(与问题无关),但是 ""string.Empty 一样有效,更易于阅读,并且不容易被反射滥用... typeof(string).GetField("Empty").SetValue(null, " "); // evil - Marc Gravell
2
@MarcGravell StyleCop建议使用string.Empty而不是“”。我不确定其中的理由是赞成还是反对。 - user743382
7
老实说,我认为string.Empty更容易阅读。这一点有些因人而异。 - Jon Hanna

15

个人而言,我会在上游进行过滤/归一化处理,但如果我必须在这里处理:


// check different, treating null & "" as equivalent
if ((person.State ?? "") != (source.State ?? ""))

刚想发同样的帖子。最好在前期就强制执行这个要求,而不是在后期修改代码。当然,有时候存在这种情况,使得这种做法不切实际,但更多时候是可能的,也不会过于困难。+1 - Ed S.

12

虽然其他答案很好,但我会将它们单独放在一个方法中,以使读者更清楚明白:

public static bool StatesEqual(string first, string second)
{
  return first ?? "" == second ?? "";
}

如果你需要在多个地方比较这些状态,或者需要处理其他特殊情况,这将非常有益。 (例如,将其更改为不区分大小写,或者如果两个状态在文本上不同但一个是另一个的缩写,即您希望"WI"等于"Wisconsin"。


更多信息: "==" 本质上是 x.Equals(y),因此它执行“序数(区分大小写和不考虑文化)比较。”(MSDN) - Philm
这是一个好的解决方案的原因:+1:“==”是最简洁的风格(如果可能使用)。+1:如果你想要将“null == null”的结果设为true,这是合乎逻辑的,但与C#标准定义的“==”和Equals相反。+1:序数和不区分大小写可能是大多数初学者期望的,并且专家应该知道这里的标准。 - Philm
当这种方法通常不太合适时:-1:您不想破坏标准的“Equals”行为,“null等于无”。-1:您更喜欢详细说明您当前使用的文化和大小写(此处为StringComparison.Ordinal)。-1:您需要特殊的行为,尤其是不区分大小写(StringComparison.OrdinalIgnoreCase)或其他内容。-1:您不想在代码中添加手动处理空值的代码(在我看来:这里是正确的位置,但请尝试通过其他方式避免任何条件语句,以确保在此之前值不能为空)。 - Philm

3

你可能会认为,在String.Equals中应该有一个StringComparison枚举值来处理这个问题,或者在String.Compare中有一个CompareOptions枚举值来处理它,但实际上并没有。

无论如何,我认为最好的做法仍然是使用String.Equals。

string s1 = null;
string s2 = string.Empty;

bool areEqual = string.Equals(s1 ?? string.Empty, s2 ?? string.Empty);

// areEqual is now true.

就像这样,您可以轻松添加大小写或区分文化字符串比较选项...

bool areEqual = string.Equals(s1 ?? string.Empty, s2 ?? string.Empty, StringComparison.OrdinalIgnoreCase);

2
这听起来像是一个扩展方法的完美解决方案。
    public static bool IsEqualNoNulls(this String str, string cmp) //bad name, but you get the point
    {
        return (str ?? "") == (cmp ?? "");
    }

你可以选择使用扩展方法的参数或直接使用扩展方法体,我更倾向于后者,因为我认为这不是一个很大的风格问题。


2

String类有一个名为"IsNullOrEmpty"的函数,接受一个字符串作为参数。

http://msdn.microsoft.com/en-us/library/system.string.isnullorempty.aspx

根据文档:

IsNullOrEmpty是一种方便的方法,可同时测试一个字符串是否为null或其值是否为空。它等同于以下代码:

result = s == null || s == String.Empty;

例如:

if (!(string.IsNullOrEmpty(person.State) && string.IsNullOrEmpty(source.State)))
{
      //update your data . .
}

或者您可以使用扩展方法,类似于@Earlz所概述的方法。

您可以在这里了解更多关于它们的信息http://msdn.microsoft.com/en-us/library/bb383977.aspx

因此,假设我有一个如下的扩展方法:

public static bool IsBlank(this string str)
{
    return string.IsNullOrEmpty(str);
}

这将使您能够做类似于以下的事情:
if(!(person.State.IsBlank() && source.State.IsBlank())
{
     //do something
}

即使person.State或source.State为null,这个方法仍然有效,因为这个扩展方法看起来像是字符串类的方法,但实际上被转换为以字符串变量作为参数的静态方法(按照文档),所以即使该字符串变量没有设置为字符串的实例,它也可以愉快地工作。

请注意,如果您在阅读代码并尝试弄清楚当person.State或source.State设置为null时为什么会有效时,这种方法可能会在以后困扰您 :P

或者,你知道的,另一种方式就是将其完全写出来 :)


1
你现在不再比较这两个值了。 - leora
啊,非常好的观点。我想我忽略了代码的原始目的。 - jonathanl

1

所有给出的答案都无法通过土耳其测试。请尝试使用这个:

public static bool StatesEqual(string first, string second)
{
    if (first == null || second == null)
        return false; // You can also use return first == second if you want to compare null values.

    return first.Equals(second, StringComparison.InvariantCulture);
}

我喜欢这个,但需要说明的是,如果一个人更喜欢二进制比较(例如更快),并且想要忽略语言特性,那么StringComparison.OrdinalIgnoreCase更合适。或者按照MSDN中推荐的“在.NET Framework中使用字符串的最佳实践”,只有当比较与语言相关时才使用InvariantCulture。没有更好或更差的选择,这取决于您的偏好。 - Philm
举个例子,我经常需要比较德语“文化”区域的文本,但特别是InvariantCulture的行为不适合我:InvariantCulture认为“ss”等于“ß”,这在大多数情况下并不是我想要的,因为在德语中它们绝对不相同,此外,作为母语者,我认为这绝对是一个错误的选择(由英语语言学家),但对于“InvariantCultureIgnoreCase”是可以接受的。 - Philm
请点击此处查看InvariantCulture和Ordinal的区别:https://dev59.com/WXRB5IYBdhLWcg3w1Kv0 - Philm
还有一个注释:如果您想处理不区分大小写的比较或ToUpper()/ToLower()等内容,则土耳其测试大多是相关的。因此,“==”足够并且是一种良好的区分大小写的比较样式。 - Philm

1
我需要创建自己的比较器对象吗?因为这似乎是一个通用问题。
从这里得到的好答案应该已经很清楚了,你不需要创建自己的比较器对象。但如果你需要反复进行此类比较,或者想要使用状态作为键,则可以考虑创建自己的比较器对象。
public class NullEmptStringComparer : IComparer<string>
{
  public Equals(string x, string y)
  {
    return (x ?? string.Empty) == (y ?? string.Empty);
  }
  public int GetHashCode(string str)
  {
    return (str ?? string.Empty).GetHashCode();
  }
}

或者基于另一个比较来进行,以防默认的==比较不适用(实际上很少适用):

public class NullEmptCustStringComparer : IComparer<string>
{
  private readonly IComparer<string> _baseCmp;
  public NullEmptCustStringComparer(IComparer<string> baseCmp)
  {
    _baseCmp = baseCmp;
  }
  public Equals(string x, string y)
  {
    return _baseCmp.Equals(x ?? string.Empty, y ?? string.Empty);
  }
  public int GetHashCode(string str)
  {
    return _baseCmp.GetHashCode(str ?? string.Empty);
  }
}

0

空和null并不是同一回事,因此您并没有涉及到通用问题。您的问题属于领域问题,因为您的业务规则要求特定的评估结果为真。您可以编写一个类似以下代码的方法:

public static bool AreMyStringsCustomEqual(string s1, string s2) {
    return (s1 == null || s1 == "" && s2 == null || s2 == "");
}

或者类似这样的东西。然后从任何地方调用它。你甚至可以将其作为扩展方法。


3
除了 null 或 "" 以外的任何值都无法满足整体平等条件;例如,对于 "abc" 和 "abc" 返回 false(错误),而对于 null 和 "abc" 以及 "abc" 和 "",返回 true(错误)。 - Marc Gravell

0

我认为这是使用装饰器模式的情况。您需要装饰一个股票StringComparer以实现您想要的功能:

public enum Collapse
{
  None                      = 0 ,
  EmptyAndWhitespace        = 1 ,
  NullAndWhitespace         = 2 ,
  NullAndEmpty              = 3 ,
  NullAndEmptyAndWhitespace = 4 ,
}

public class MySpecialStringComparerDecorator : StringComparer
{
  const   string         COLLAPSED_VALUE = "" ;
  private StringComparer instance ;
  private Collapse     rule     ;

  public StringComparer Decorate( StringComparer sc , Collapse equivalencyRule )
  {
    StringComparer instance = new MySpecialStringComparer( sc , equivalencyRule ) ;
    return instance ;
  }

  private MySpecialStringComparerDecorator( StringComparer comparer , Collapse equivalencyRule )
  {
    if ( comparer == null                                  ) throw new ArgumentNullException("comparer") ;
    if ( !Enum.IsDefined(typeof(Collapse),equivalencyRule) ) throw new ArgumentOutOfRangeException("equivalencyRule") ;

    this.instance = comparer ;
    this.rule     = equivalencyRule ;

    return ;
  }

  private string CollapseAccordingToRule( string s )
    {
        string collapsed = s ;
        if ( rule != Collapse.None )
        {
            if ( string.IsNullOrWhiteSpace(s) )
            {
                bool isNull  = ( s == null ? true : false ) ;
                bool isEmpty = ( s == ""   ? true : false ) ;
                bool isWS    = !isNull && !isEmpty ;

                switch ( rule )
                {
                    case Collapse.EmptyAndWhitespace        : if ( isNull||isWS          ) collapsed = COLLAPSED_VALUE ; break ;
                    case Collapse.NullAndEmpty              : if ( isNull||isEmpty       ) collapsed = COLLAPSED_VALUE ; break ;
                    case Collapse.NullAndEmptyAndWhitespace : if ( isNull||isEmpty||isWS ) collapsed = COLLAPSED_VALUE ; break ;
                    case Collapse.NullAndWhitespace         : if ( isNull||isWS          ) collapsed = COLLAPSED_VALUE ; break ;
                    default                                 : throw new InvalidOperationException() ;
                }
            }
        }
        return collapsed ;
    }

  public override int Compare( string x , string y )
  {
    string a     = CollapseAccordingToRule(x) ;
    string b     = CollapseAccordingToRule(y) ;
    int    value = instance.Compare(a,b);
    return value ;
  }

  public override bool Equals( string x , string y )
  {
    string a     = CollapseAccordingToRule(x) ;
    string b     = CollapseAccordingToRule(y) ;
    bool   value = instance.Equals(a,b) ;
    return value ;
  }

  public override int GetHashCode( string obj )
  {
    string s     = CollapseAccordingToRule(obj) ;
    int    value = instance.GetHashCode( s ) ;
    return value ;
  }

}

使用方法很简单:

StringComparer sc = new MySpecialStringDecorator( StringComparer.CurrentCultureIgnoreCase , Collapse.NullAndEmptyAndWhitespace ) ;

// go to town

7
看起来这是一个过度设计的完美例子。 - Earlz
“过度设计”将涉及一个工厂类,该类将返回一个经过适当装饰的StringComparer,基于文化 - 当前、不变、序数或特定 - 大小写敏感性和等效规则,并为每个等效规则的装饰器创建一个单独的子类型。 :D - Nicholas Carey

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