13

I have the following code (as i am trying to detect changes to a field)

 if (person.State != source.State)
 {
      //update my data . .
  }

the issue is I am having cases where person.State is NULL and source.State is "" and thus returning true.

If one is null and the other is an empty string, I want to treat them as equal and don't update my data. What is the cleanest way of doing that? Do i need to create my own Comparer object as this seems like a generic problem

leora
  • 188,729
  • 360
  • 878
  • 1,366
  • BTW Is there a public string comparison routine in `Microsoft.VisualBasic` that implements this, seeing as this is the default comparison for VB.NET Strings? – Mark Hurd Aug 19 '12 at 01:09

10 Answers10

23

You could, if you really need to, do:

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

However, depending on your needs, a better solution might be to modify your person.State property to never return null values.

public class Person
{
    string _state = string.Empty;
    public string State
    {
        get { return _state; }
        set { _state = value ?? string.Empty; }
    }
}
  • 2
    A very minor observation (unrelated to the question), but `""` is just as efficient as `string.Empty`, is easier to read, and is not subject to abuse by reflection... `typeof(string).GetField("Empty").SetValue(null, " "); // evil` – Marc Gravell Aug 16 '12 at 21:26
  • 2
    @MarcGravell [StyleCop recommends string.Empty instead of ""](http://stylecop.soyuz5.com/SA1122.html). I'm not sure about the reasons for or against. –  Aug 16 '12 at 21:30
  • 7
    @MarcGravell I honestly find `string.Emtpy` easier to read. There's a bit of the eye of the beholder to that one. – Jon Hanna Aug 16 '12 at 21:38
14

Personally, I'd sanitize/normalize upstream, but if I had to do it here:

// check different, treating null & "" as equivalent
if ((person.State ?? "") != (source.State ?? ""))
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Was just going to post the same thing. Better to enforce this requirement up front rather than finagling your code on the back end. Of course, sometimes situations exist which make this sort of thing impractical, but more often than not is possible and not overly difficult. +1 – Ed S. Aug 16 '12 at 21:27
12

While the other answers are good, I would pull them out into their own method to make it clearer to the reader:

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

This will be beneficial if you compare these states in multiple places, or allow you to handle other odd cases if there are any. (Examples might be to change it to be case insensitive, or if two states are textually different but one is an abbreviation of another, i.e. you want "WI" to be equal to "Wisconsin".

Servy
  • 202,030
  • 26
  • 332
  • 449
  • For more information: "==" is intrinsically x.Equals(y) and so it performs an "ordinal (case-sensitive and culture-insensitive) comparison." (MSDN) – Philm Apr 26 '17 at 11:22
  • What makes this a good solution: +1: "==" is for most the most concise style (if possible to use). +1: If you want to result "null==null" to true which is logical but the opposite to C# standard definition of "==" and Equals. +1: ordinal and case-insensitive may (?) be what most beginners would expect and what experts should know as standard here. – Philm Apr 26 '17 at 11:36
  • When this approach is not so appropriate generally: -1: You don't want to break standard "Equals" behavior "null is equal to nothing". -1: You prefer to be verbose about the culture and casing you use currently (here: StringComparison.Ordinal). -1: You need special behavior, especially insensitive casing (StringComparison.OrdinalIgnoreCase) or something else. -1: You don't want to clutter your code with manual handling of values being null (IMO: here is the right place for such, but: Try to avoid such conditionals everywhere by other means as to assure before that the values cannot be null.) – Philm Apr 26 '17 at 12:15
3

You'd think there would be a StringComparison enum value to handle this with String.Equals... or a CompareOptions enum value to handle it with String.Compare... but there is not.

In any case, I think you should still be using String.Equals as a best practice.

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

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

// areEqual is now true.

And like this you can add case or culture string compare options easily...

bool areEqual = string.Equals(s1 ?? string.Empty, s2 ?? string.Empty, StringComparison.OrdinalIgnoreCase);
James
  • 2,823
  • 22
  • 17
2

This sounds like a perfect solution for a extension method.

    public static bool IsEqualNoNulls(this String str, string cmp) //bad name, but you get the point
    {
        return (str ?? "") == (cmp ?? "");
    }

.... or just using the body of the extension method, which I'd probably prefer, as I don't see this as too much of a style problem.

Earlz
  • 62,085
  • 98
  • 303
  • 499
2

The String class has a function "IsNullOrEmpty" which takes in a string.

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

From the documentation:

IsNullOrEmpty is a convenience method that enables you to simultaneously test whether a String is null or its value is Empty. It is equivalent to the following code:

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

For example:

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

Alternatively you could use an extension method, similar to what is outlined by @Earlz

You can learn more about them here http://msdn.microsoft.com/en-us/library/bb383977.aspx

Therefore, assuming I had an extension method like the following:

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

This will allow you to do something like

if(!(person.State.IsBlank() && source.State.IsBlank())
{
     //do something
}

The reason this works, even if person.State or source.State is null is because the extension method, while looking like a method of the string class, is actually converted to a static method with the string variable as it's argument (as per the documentation), so it'll happily work even if the string variable isn't set to an instance of string.

Keep in mind, though, that doing it this way could trip you up at a later time if you're reading the code and trying to figure out why it works when person.State or source.State is set to null :P

Or, y'know, alternatively I'd just write out a comparison in full :)

jonathanl
  • 524
  • 2
  • 9
1

Do i need to create my own Comparer object as this seems like a generic problem

It should be clear by now from the good answers here, that you don't, but if you are doing this sort of comparison over and over, or want to use the states as keys, then:

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();
  }
}

Or to base it on another comparison, in case the default == comparison isn't appropriate (which it rarely is, really):

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);
  }
}
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
1

All given answers will fail the Turkey Test. Try this one instead:

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);
}
MrSoundless
  • 1,246
  • 2
  • 12
  • 34
  • I like this one, but it is to state that if one prefers binary compare (e.g. faster) and wants to ignore linguistic specialties, then StringComparison.OrdinalIgnoreCase is more appropriate. Or as it is recommended in MSDN "Best Practices for Using Strings in the .NET Framework", use InvariantCulture only when the comparison is linguistically relevant. There is no better or worse, it depends on your preferences. – Philm Apr 26 '17 at 12:01
  • One example, I have often to compare texts for German "culture" locale, but especially the behavior of the InvariantCulture which tries to consider a specialty of German and similar languages doesn't fit for me: InvariantCulture considers "ss" being equal to "ß" which is for most cases not what I want, as it is definitely not the same in German, moreover I would consider this definitely a wrong choice (by English linguists) for "InvariantCulture" as a native speaker, but agreeable for "InvariantCultureIgnoreCase". – Philm Apr 26 '17 at 12:05
  • See here for InvariantCulture and Ordinal: http://stackoverflow.com/questions/492799/difference-between-invariantculture-and-ordinal-string-comparison – Philm Apr 26 '17 at 12:08
  • One more comment: The Turkey test is mostly relevant, if you want to handle a comparison case-insensitive or ToUpper()/ToLower() stuff. So "==" is sufficient and a good case-sensitive comparison style. – Philm Apr 26 '17 at 12:12
0

Well, empty and null are not the same thing, so you're not referring to a generic problem here. Yours is a domain problem where your business rules demand that particular evaluation as being true. You can always write a method that looks like this:

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

Or something like that. And then call it from everywhere. You could even make it an extension method.

kprobst
  • 16,165
  • 5
  • 32
  • 53
  • 3
    this fails to apply the overall equality condition for any values other than null or ""; for example, it returns false for "abc" and "abc" (wrong) and returns true for null and "abc" (wrong), and true for "abc" and "" (wrong) – Marc Gravell Aug 16 '12 at 21:48
0

I believe this is a case for the Decorator pattern. You need to decorate a stock StringComparer to do what you want:

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 ;
  }

}

Usage is simple:

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

// go to town
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • 7
    Looks like a perfect example of over-engineering – Earlz Aug 17 '12 at 00:49
  • "Over-engineered" would involve a factory class that would return a properly decorated `StringComparer` based on culture — current, invariant, ordinal or specific — case-sensitivity and equivalency rule, with the decorator for each equivalency rule an individual subtype. :D – Nicholas Carey Aug 17 '12 at 04:03