Many .NET projects use well-known GUIDs as identifiers for activities, interop scenarios, database keys, and other purposes. It's common to see code like new Guid("01234567-8901-2345-6789-012345678901") throughout applications. While this approach is readable and straightforward, it comes with a hidden performance cost that can impact application startup time.
Creating a GUID from a string requires parsing the string representation, which involves several operations that add overhead. Also, .NET supports multiple formats for GUID strings (with or without braces, hyphens, etc.), which adds complexity to the parsing logic.
If the GUID is hardcoded in the source code, you can avoid this overhead by using the Guid constructor that accepts numeric parameters directly.
The string-based GUID creation:
C#
var guid = new Guid("01234567-8901-2345-6789-012345678901");
Can be rewritten as:
C#
var guid = new Guid(0x01234567, 0x8901, 0x2345, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01);
This approach constructs the GUID directly from its numeric components, bypassing the need for string parsing. However, this method is less readable and requires converting the GUID string into its numeric parts, which can be error-prone if done manually. Also, it's more difficult to search for specific GUIDs in the codebase. A solution is to add the string representation as a comment next to the numeric constructor for clarity.
#Automated Detection with Meziantou.Analyzer
To help identify opportunities for this optimization, Meziantou.Analyzer includes rule MA0176 that detects GUID creation from string literals and suggests using the numeric constructor instead.
To install Meziantou.Analyzer, you can use the .NET CLI or add it directly to your project file.
Add the analyzer to your project using the .NET CLI:
Shell
dotnet add package Meziantou.Analyzer
Or add it directly to your project file:
XML
<PackageReference Include="Meziantou.Analyzer" Version="2.0.224">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
The rule MA0176 identifies these patterns and suggests optimized alternatives:
C#
// ❌ String-based creation (detected by analyzer)
_ = new Guid("01234567-8901-2345-6789-012345678901");
_ = Guid.Parse("01234567-8901-2345-6789-012345678901");
The analyzer automatically applies the fix while preserving the original string representation in a comment for maintainability:
C#
// ✅ Optimized numeric constructor (suggested fix)
new Guid(0x01234567, 0x8901, 0x2345, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01) /* 01234567-8901-2345-6789-012345678901 */;
Example of MA0176 in Visual Studio
Here's the BenchmarkDotNet code used to measure the performance difference between the approaches:
C#
public class NewGuid
{
[Benchmark(Baseline = true)]
public Guid GuidParse() => Guid.Parse("01234567-8901-2345-6789-012345678901");
[Benchmark]
public Guid NewGuidString() => new Guid("01234567-8901-2345-6789-012345678901");
[Benchmark]
public Guid NewGuidComponents() => new Guid(0x01234567, 0x8901, 0x2345, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01);
}
| Method | Mean | Error | StdDev | Median |
|---|
| GuidParse | 23.4168 ns | 0.4823 ns | 0.4511 ns | 23.3622 ns |
| NewGuidString | 22.6531 ns | 0.3757 ns | 0.3514 ns | 22.7011 ns |
| NewGuidComponents | 0.0215 ns | 0.0170 ns | 0.0366 ns | 0.0000 ns |
While there is a huge performance difference in micro-benchmarks (*1000), most applications have only a few constant GUIDs, so the overall impact on performance is minimal. However, in application startup, every small optimization can contribute to a better user experience. Also, in Azure Functions or AWS Lambda scenarios, where cold start time is critical, this optimization can help reduce latency.
#Additional Resources
Do you have a question or a suggestion about this post? Contact me!