String comparison with StringComparer

Using StringComparer.OrdinalIgnoreCase.Equals to compare strings instead of ToLower

Home DailyDrop

Daily Knowledge Drop

When comparing strings, instead of using ToLower() (or ToUpper) with the comparison operator ==, StringComparer.OrdinalIgnoreCase.Equals can be used, which is faster and uses less memory.


Examples

There are a number of different ways to compare strings in C# - this post will explore a number of these methods, as well as compare their performance.


==

The most basic method is to use the comparison operator ==.

var value1 = "String1";
var value2 = "STRING1";
var value3 = "String2";

Console.WriteLine("Using ==");
Console.WriteLine($"value1 and value2: {value1 == value2}");
Console.WriteLine($"value1 and value3: {value1 == value3}");

The output:

Using ==
value1 and value2: False
value1 and value3: False

The issue with this approach is that the case of the string is not taken into account.


ToLower/ToUpper

An often use method, is to use the comparison operator, but to ToUpper or ToLower the string for comparison:

var value1 = "String1";
var value2 = "STRING1";
var value3 = "String2";

Console.WriteLine("Using == and ToLower");
Console.WriteLine($"value1 and value2: {value1.ToLower() == value2.ToLower()}");
Console.WriteLine($"value1 and value3: {value1.ToLower() == value3.ToLower()}");
Console.WriteLine("-----");

Console.WriteLine("Using == and ToUpper");
Console.WriteLine($"value1 and value2: {value1.ToUpper() == value2.ToUpper()}");
Console.WriteLine($"value1 and value3: {value1.ToUpper() == value3.ToUpper()}");

The output:

Using == and ToLower
value1 and value2: True
value1 and value3: False
-----
Using == and ToUpper
value1 and value2: True
value1 and value3: False

Both methods yield the same, accurate result.


string.Equals

The string class has an Equals method, which can also be used for comparison. One version is a static method, and the other is an instance method.

var value1 = "String1";
var value2 = "STRING1";
var value3 = "String2";

// instance Equals method
Console.WriteLine("Using instance Equals");
Console.WriteLine($"value1 and value2: {value1.Equals(value2)}");
Console.WriteLine($"value1 and value3: {value1.Equals(value3)}");
Console.WriteLine("-----");

// Static Equals method
Console.WriteLine("Using static Equals");
Console.WriteLine($"value1 and value2: {string.Equals(value1, value2)}");
Console.WriteLine($"value1 and value3: {string.Equals(value1, value3)}");

The output:

Using instance Equals
value1 and value2: False
value1 and value3: False
-----
Using static Equals
value1 and value2: False
value1 and value3: False

This basic version of Equals does not take the string case into account - however there is an additional parameter which can be passed to the Equals method to define how the comparison is done:

var value1 = "String1";
var value2 = "STRING1";
var value3 = "String2";

Console.WriteLine("Using static Equals OrdinalIgnoreCase");
Console.WriteLine($"value1 and value2: {string.Equals(value1, value2, 
    StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($"value1 and value3: {string.Equals(value1, value3, 
    StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine("-----");

The output of this:

Using string.Equals OrdinalIgnoreCase
value1 and value2: True
value1 and value3: False

StringComparer

The final method is using StringComparer:

var value1 = "String1";
var value2 = "STRING1";
var value3 = "String2";

Console.WriteLine("Using StringComparer");
Console.WriteLine($"value1 and value2: {StringComparer.OrdinalIgnoreCase.Equals(value1, value2)}");
Console.WriteLine($"value1 and value3: {StringComparer.OrdinalIgnoreCase.Equals(value1, value3)}");

The output:

Using StringComparer
value1 and value2: True
value1 and value3: False

Benchmarks

Finally, we can benchmark all the different methods:

Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
BasicComparison 1.972 ns 0.0392 ns 0.0348 ns 1.974 ns 1.00 0.00 - - NA
BasicToLower 42.073 ns 0.8145 ns 0.7619 ns 42.187 ns 21.34 0.61 0.0127 80 B NA
BasicToUpper 51.095 ns 4.6650 ns 13.7548 ns 42.910 ns 21.74 1.32 0.0127 80 B NA
StringInstanceEquals 1.903 ns 0.0465 ns 0.0388 ns 1.912 ns 0.96 0.02 - - NA
StringStaticEquals 1.896 ns 0.0272 ns 0.0227 ns 1.899 ns 0.96 0.02 - - NA
StringEqualOrdinalIgnoreCases 6.083 ns 0.1480 ns 0.1584 ns 6.078 ns 3.09 0.10 - - NA
StringComparerOrdinalIgnoreCases 4.770 ns 0.0669 ns 0.0559 ns 4.762 ns 2.42 0.04 - - NA

From the results, once can see that:

  • In all instances where the case of the string is taken into account, performance is slower
  • The often used ToUpper/ToLower method is the slowest a large margin, and the only method to use memory
  • StringComparer is the most performant technique to use when a comparison needs to be done while ignoring the case

Notes

Realistically, in most applications the performance of a string comparison is not going to have any material effect on performance. However if the application does a large number of string comparisons, the accumulative effect could be slightly noticeable and it might be worth investigating the use of StringComparer.


References

@Tullo Tweet

Daily Drop 201: 14-11-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net string compare comparison