NDepend Blog

Improve your .NET code quality with NDepend

.NET Micro-Optimization and Refactoring Trick

May 31, 2023 3 minutes read

Recently, I made an interesting observation regarding Dictionary<string,T>: the method TryGetValue() is faster when building  with new Dictionary<string,T>(StringComparer.Ordinal). This performance difference can be attributed to the fact that StringComparer.Ordinal performs a raw byte comparison of string characters. I googled a bit and stumbled on this 2019 runtime note String Dictionary with StringComparer.Ordinal slower than default EqualityComparer.

I updated the benchmark to run against  net7.0, net6.0, net481, net472 (see the benchmark code at the end) and got this result:

Dictionary of string is faster with StringComparer.Ordinal only on .NET Fx platform but slower on .NET Core ones that are so optimized that the default EqualityComparer performs better.

So I built a method to use StringComparer.Ordinal only within .NET Fx context. Let’s precise that most of NDepend code is compiled against netstandard2.0 since it runs within VisualStudio .NET Fx process. But it can also run on a Mac or Linux platform above .NET 5/6/7 and soon 8 (more on this here 5x Lessons Learned from Migrating a Large Legacy to .NET 5/6).

s_Kind is inferred from this trick.

Now all calls to new Dictionary<string,T>() must be refactored into calls to DotNetRuntime.BuildDictionaryOfString<T>().

Refactoring with Resharper Replace with Pattern

Thanks to recent enhancements in Visual Studio 2022 I find myself relying less on Resharper than before. However there is one Resharper awesome feature when it comes to refactoring Replace with Pattern also named Structural Search and Replace. One specific example of its usefulness is when searching for the pattern new Dictionary<string,$T$>() with T being a type expression 99 occurrences of new Dictionary<string,T>() to refactor are matched.

Resharper Search Template

To refactor these occurrences there is a Replace mode in the dialog Change Search Pattern that lets write this Replace pattern: DotNetRuntime.BuildDictionaryOfString<$T$>()

We can see how this feature outperforms a classic textual Find and Replace:

  • It knows about C# and will find expression no matter the formatting (more or less space characters) and comments.
  • Placeholder like $T$ can be provided with a specific meaning. Here $T$ must be a type C# expression, but it could be an identifier, a statement, an argument or an expression.
  • Placeholder can be re-used in the Replace pattern to customize the refactoring.

Click Replace, then the next dialog let’s choose where exactly the refactoring should happen:

Jetbrains Replace Pattern 2

Finally Resharper does the job. However it was not smart enough to import the necessary using statements so I had to do it manually:

Jetbrains Replace Pattern 3

Conclusion

This is a two in one post, with a micro-performance point for those that are still on .NET Framework and a cool Resharper Refactoring tool.

Interestingly enough benchmark shows that HashSet<T> doesn’t run faster with StringComparer.Ordinal.

The Benchmark Code

The C# code:

The .csproj content: