The efficiency of StringComparer.CurrentCultureIgnoreCase with multiple calls in .NET.

I used StringComparer.CurrentCultureIgnoreCase

for case insensitive comparisons and hashing. But after checking the referenced source, I can see that it creates a new instance with every call (shouldn't it be a static function then? Just for the sake of the form). Anyway, my question is, when you have multiple comparisons, like an implementation IEquality<T>

, is it efficient to do this:

// 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 / merge the mappings so they don't get created every time they are called Equals()

?

// 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 senses that are best to practice on?

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

+3


source to share


2 answers


According to the reference source , OrdinalIgnoreCase returns the same static instance every 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 actual reallocated .NET files, the remaining field access will almost certainly be embedded in jitter.

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



On the other hand, CurrentCulture and CurrentCultureIgnoreCase return new instances every time you access them, because the current culture can change between hits. Should the comparator be cached in this case? Personally, I wouldn't make the code more complex unless profiling showed that there was a problem.

In this particular case, however, 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 the StringComparer allocation at all, even if you use CurrentCulture or CurrentCultureIgnoreCase and the code is still easy to read.

+4


source


Never underestimate the cost of creating threadless code. CurrentCulture is a thread property and of course different threads can work with different cultures. You will need a cache that can be streamed to store objects. A cache without a retirement policy is a memory leak, now you also need to keep track of the most recent usage and how you delete objects that have not been used for a while. Nobody is obvious.

It's just easier and cheaper to create an object when needed. It's pretty small, cheaper than strings. This is highly unlikely. Memory allocated from gen # 0 that doesn't get promoted is very cheap.



The .NET Framework is highly optimized, they didn't mess up this file.

+1


source







All Articles