Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StringComparer.CurrentCultureIgnoreCase efficiency with multiple calls in .NET

I've been using StringComparer.CurrentCultureIgnoreCase for case-insensitive comparisons and hashing. But after checking the Reference Source I see it creates a new instance with every call (shouldn't it be a static function then? Just for form's sake). Anyway my question is, when you have multiple comparisons to do, like an IEquality<T> implementation, is it efficient to do:

// 2 instances per call
return StringComparer.CurrentCultureIgnoreCase.Equals(this.a, other.a)
  && StringComparer.CurrentCultureIgnoreCase.Equals(this.b, other.b) .. etc ..

Or maybe:

public bool Equals(MyObj other)
{
  // 1 instance per call
  var equ = StringComparer.CurrentCultureIgnoreCase;
  return equ.Equals(this.a, other.a)
    && equ.Equals(this.b, other.b) .. etc ..
}

Or even cache/pool the comparers so they arn't created every time Equals() is called?

// 1 instance per thread
[ThreadStatic]
private static StringComparer equ;

public bool Equals(MyObj other)
{
  if (equ == null) equ = StringComparer.CurrentCultureIgnoreCase;

  return equ.Equals(this.a, other.a)
    && equ.Equals(this.b, other.b) .. etc ..
}

Any feelings on which is best practice?

(Thanks to michael-liu for pointing out by original reference to OrdinalIgnoreCase is not a new instance, I've switched to CurrentCultureIgnoreCase which is)

like image 256
user826840 Avatar asked Mar 16 '23 19:03

user826840


1 Answers

According to the reference source, OrdinalIgnoreCase returns the same static instance each time:

public abstract class StringComparer : ...
{
    ...

    private static readonly StringComparer _ordinalIgnoreCase = new OrdinalComparer(true);        

    ...

    public static StringComparer OrdinalIgnoreCase { 
        get {
            Contract.Ensures(Contract.Result<StringComparer>() != null);
            return _ordinalIgnoreCase;
        }
    }

Since the Contract.Ensures call is omitted in the actual .NET redistributables, the remaining field access will almost certainly be inlined by the jitter.

(The same applies to InvariantCulture, InvariantCultureIgnoreCase, and Ordinal.)

On the other hand, CurrentCulture and CurrentCultureIgnoreCase do return new instances each time you access them because the current culture may change between accesses. Should you cache the comparer in this case? Personally, I wouldn't make my code more complicated unless profiling indicated there was a problem.

In this particular case, though, I usually compare strings for equality like this:

return String.Equals(this.a, other.a, StringComparison.OrdinalIgnoreCase);

Now you don't have to worry about StringComparer allocations at all, even if you use CurrentCulture or CurrentCultureIgnoreCase, and the code is still straightforward to read.

like image 182
Michael Liu Avatar answered Mar 19 '23 07:03

Michael Liu