Skip to content

Commit 6dba88a

Browse files
authored
Merge pull request #31 from BenjaminAbt/feature/features-and-enhancements
2 parents 2b50fb0 + 4819b63 commit 6dba88a

File tree

91 files changed

+5775
-61
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+5775
-61
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ When creating concrete domain types in `StrongOf.Domains`, follow this pattern:
182182
[TypeConverter(typeof(UserIdTypeConverter))]
183183
public sealed class UserId(Guid value) : StrongGuid<UserId>(value)
184184
{
185-
// Domain-specific methods only no redundant overrides
185+
// Domain-specific methods only - no redundant overrides
186186
public bool HasValue() => Value != Guid.Empty;
187187
}
188188

BenjaminAbt.StrongOf.sln

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrongOf.Domains", "src\Str
4747
EndProject
4848
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrongOf.Domains.UnitTests", "tests\StrongOf.Domains.UnitTests\StrongOf.Domains.UnitTests.csproj", "{F739C970-C79D-4F02-9F49-094A02753C92}"
4949
EndProject
50+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrongOf.EntityFrameworkCore.UnitTests", "tests\StrongOf.EntityFrameworkCore.UnitTests\StrongOf.EntityFrameworkCore.UnitTests.csproj", "{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}"
51+
EndProject
52+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrongOf.EntityFrameworkCore", "src\StrongOf.EntityFrameworkCore\StrongOf.EntityFrameworkCore.csproj", "{8CAB9635-9405-4817-9072-D8309E07306E}"
53+
EndProject
54+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrongOf.OpenApi", "src\StrongOf.OpenApi\StrongOf.OpenApi.csproj", "{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}"
55+
EndProject
56+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrongOf.MinimalApis", "src\StrongOf.MinimalApis\StrongOf.MinimalApis.csproj", "{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}"
57+
EndProject
5058
Global
5159
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5260
Debug|Any CPU = Debug|Any CPU
@@ -177,6 +185,54 @@ Global
177185
{F739C970-C79D-4F02-9F49-094A02753C92}.Release|x64.Build.0 = Release|Any CPU
178186
{F739C970-C79D-4F02-9F49-094A02753C92}.Release|x86.ActiveCfg = Release|Any CPU
179187
{F739C970-C79D-4F02-9F49-094A02753C92}.Release|x86.Build.0 = Release|Any CPU
188+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
189+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
190+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Debug|x64.ActiveCfg = Debug|Any CPU
191+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Debug|x64.Build.0 = Debug|Any CPU
192+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Debug|x86.ActiveCfg = Debug|Any CPU
193+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Debug|x86.Build.0 = Debug|Any CPU
194+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
195+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Release|Any CPU.Build.0 = Release|Any CPU
196+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Release|x64.ActiveCfg = Release|Any CPU
197+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Release|x64.Build.0 = Release|Any CPU
198+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Release|x86.ActiveCfg = Release|Any CPU
199+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0}.Release|x86.Build.0 = Release|Any CPU
200+
{8CAB9635-9405-4817-9072-D8309E07306E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
201+
{8CAB9635-9405-4817-9072-D8309E07306E}.Debug|Any CPU.Build.0 = Debug|Any CPU
202+
{8CAB9635-9405-4817-9072-D8309E07306E}.Debug|x64.ActiveCfg = Debug|Any CPU
203+
{8CAB9635-9405-4817-9072-D8309E07306E}.Debug|x64.Build.0 = Debug|Any CPU
204+
{8CAB9635-9405-4817-9072-D8309E07306E}.Debug|x86.ActiveCfg = Debug|Any CPU
205+
{8CAB9635-9405-4817-9072-D8309E07306E}.Debug|x86.Build.0 = Debug|Any CPU
206+
{8CAB9635-9405-4817-9072-D8309E07306E}.Release|Any CPU.ActiveCfg = Release|Any CPU
207+
{8CAB9635-9405-4817-9072-D8309E07306E}.Release|Any CPU.Build.0 = Release|Any CPU
208+
{8CAB9635-9405-4817-9072-D8309E07306E}.Release|x64.ActiveCfg = Release|Any CPU
209+
{8CAB9635-9405-4817-9072-D8309E07306E}.Release|x64.Build.0 = Release|Any CPU
210+
{8CAB9635-9405-4817-9072-D8309E07306E}.Release|x86.ActiveCfg = Release|Any CPU
211+
{8CAB9635-9405-4817-9072-D8309E07306E}.Release|x86.Build.0 = Release|Any CPU
212+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
213+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
214+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Debug|x64.ActiveCfg = Debug|Any CPU
215+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Debug|x64.Build.0 = Debug|Any CPU
216+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Debug|x86.ActiveCfg = Debug|Any CPU
217+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Debug|x86.Build.0 = Debug|Any CPU
218+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
219+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Release|Any CPU.Build.0 = Release|Any CPU
220+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Release|x64.ActiveCfg = Release|Any CPU
221+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Release|x64.Build.0 = Release|Any CPU
222+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Release|x86.ActiveCfg = Release|Any CPU
223+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B}.Release|x86.Build.0 = Release|Any CPU
224+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
225+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
226+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Debug|x64.ActiveCfg = Debug|Any CPU
227+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Debug|x64.Build.0 = Debug|Any CPU
228+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Debug|x86.ActiveCfg = Debug|Any CPU
229+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Debug|x86.Build.0 = Debug|Any CPU
230+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
231+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Release|Any CPU.Build.0 = Release|Any CPU
232+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Release|x64.ActiveCfg = Release|Any CPU
233+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Release|x64.Build.0 = Release|Any CPU
234+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Release|x86.ActiveCfg = Release|Any CPU
235+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F}.Release|x86.Build.0 = Release|Any CPU
180236
EndGlobalSection
181237
GlobalSection(SolutionProperties) = preSolution
182238
HideSolutionNode = FALSE
@@ -192,6 +248,10 @@ Global
192248
{E936BCE7-100B-4D33-B0CA-67D0109CEA10} = {C087DEB7-775E-41AA-A635-7961034570DC}
193249
{5FBF24C3-6953-4938-96BD-BC3ECFEC2899} = {23C114B9-080A-47BC-9FE7-673E1E0B9856}
194250
{F739C970-C79D-4F02-9F49-094A02753C92} = {C087DEB7-775E-41AA-A635-7961034570DC}
251+
{12C2E698-E5EF-44A3-B0DC-EAF502FC00C0} = {C087DEB7-775E-41AA-A635-7961034570DC}
252+
{8CAB9635-9405-4817-9072-D8309E07306E} = {C087DEB7-775E-41AA-A635-7961034570DC}
253+
{A3E7B1C2-94D5-4F8E-B6A7-2C1D3E4F5A6B} = {23C114B9-080A-47BC-9FE7-673E1E0B9856}
254+
{C5D6E7F8-1A2B-4C3D-9E8F-7A6B5C4D3E2F} = {23C114B9-080A-47BC-9FE7-673E1E0B9856}
195255
EndGlobalSection
196256
GlobalSection(ExtensibilityGlobals) = postSolution
197257
SolutionGuid = {596765F3-28D7-480C-9E4C-03DDD9F0F82C}

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<ItemGroup Label="Dependencies">
66
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
77
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
8+
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
89
</ItemGroup>
910
<ItemGroup Label="DevOps">
1011
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.133">

README.md

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
[![Build](https://github.com/benjaminabt/StrongOf/actions/workflows/ci.yml/badge.svg)](https://github.com/benjaminabt/StrongOf/actions/workflows/ci.yml)
44

5-
||StrongOf|StrongOf.AspNetCore|StrongOf.Json|StrongOf.FluentValidation|
6-
|-|-|-|-|-|
7-
|*NuGet*|[![NuGet](https://img.shields.io/nuget/v/StrongOf.svg?logo=nuget&label=StrongOf)](https://www.nuget.org/packages/StrongOf/)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.AspNetCore.svg?logo=nuget&label=StrongOf.AspNetCore)](https://www.nuget.org/packages/StrongOf.AspNetCore)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.Json.svg?logo=nuget&label=StrongOf.Json)](https://www.nuget.org/packages/StrongOf.Json)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.FluentValidation.svg?logo=nuget&label=StrongOf.FluentValidation)](https://www.nuget.org/packages/StrongOf.FluentValidation)|
5+
||StrongOf|StrongOf.AspNetCore|StrongOf.Json|StrongOf.FluentValidation|StrongOf.EntityFrameworkCore|StrongOf.OpenApi|
6+
|-|-|-|-|-|-|-|
7+
|*NuGet*|[![NuGet](https://img.shields.io/nuget/v/StrongOf.svg?logo=nuget&label=StrongOf)](https://www.nuget.org/packages/StrongOf/)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.AspNetCore.svg?logo=nuget&label=StrongOf.AspNetCore)](https://www.nuget.org/packages/StrongOf.AspNetCore)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.Json.svg?logo=nuget&label=StrongOf.Json)](https://www.nuget.org/packages/StrongOf.Json)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.FluentValidation.svg?logo=nuget&label=StrongOf.FluentValidation)](https://www.nuget.org/packages/StrongOf.FluentValidation)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.EntityFrameworkCore.svg?logo=nuget&label=StrongOf.EntityFrameworkCore)](https://www.nuget.org/packages/StrongOf.EntityFrameworkCore)|[![NuGet](https://img.shields.io/nuget/v/StrongOf.OpenApi.svg?logo=nuget&label=StrongOf.OpenApi)](https://www.nuget.org/packages/StrongOf.OpenApi)|
88

99
All [StrongOf Packages](https://www.nuget.org/packages/StrongOf) are available for .NET 8, .NET 9, .NET 10, and .NET 11.
1010

@@ -92,7 +92,7 @@ private sealed class UserId(Guid value) : StrongGuid<UserId>(value) { }
9292
| `StrongOf.Domains.Media` | `ColorHex`, `FileExtension`, `FilePath`, `Isbn`, `MimeType`, `Slug` |
9393
| `StrongOf.Domains.Software` | `SemVer` |
9494

95-
### Global Usings avoid repetitive `using` directives
95+
### Global Usings - avoid repetitive `using` directives
9696

9797
Because domain types are spread across multiple namespaces, we strongly recommend adding a single `GlobalUsings.cs` file at the root of your project. This is a standard C# feature that applies `using` directives project-wide, so you never have to repeat them in individual files.
9898

@@ -116,7 +116,7 @@ global using StrongOf.Domains.Software;
116116
After this one-time setup, all domain types are available everywhere in your project without any further imports:
117117

118118
```csharp
119-
// No using directives needed GlobalUsings.cs already covers them
119+
// No using directives needed - GlobalUsings.cs already covers them
120120
public class User
121121
{
122122
public TenantId TenantId { get; set; }
@@ -128,7 +128,7 @@ public class User
128128
}
129129
```
130130

131-
> **Tip:** Global usings are evaluated at compile-time and have zero runtime overhead. They were introduced in C# 10 / .NET 6 and are a first-class language feature not a workaround.
131+
> **Tip:** Global usings are evaluated at compile-time and have zero runtime overhead. They were introduced in C# 10 / .NET 6 and are a first-class language feature - not a workaround.
132132
133133
### Namespace naming rationale
134134

@@ -208,19 +208,72 @@ public class MyCustomStrongGuidBinder<TStrong> : StrongOfBinder
208208
}
209209
```
210210

211-
## Usage with Entity Framework
211+
## Usage with Entity Framework Core
212212

213-
Unfortunately, Entity Framework does not love generic [Value Converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions?WT.mc_id=DT-MVP-5001507), which is why you have to write it yourself.
213+
Install [StrongOf.EntityFrameworkCore](https://www.nuget.org/packages/StrongOf.EntityFrameworkCore) for first-class EF Core integration with a generic value converter that works for all strong types - no per-type converter classes needed.
214+
215+
```bash
216+
dotnet add package StrongOf.EntityFrameworkCore
217+
```
218+
219+
### Register Converters Once with `ConfigureConventions` (Recommended)
220+
221+
Register each strong type once in `ConfigureConventions` and EF Core applies the converter globally across all entities and `DbSet`s - no per-property configuration required:
214222

215223
```csharp
216-
public class UserIdValueConverter : ValueConverter<UserId, Guid>
224+
public class AppDbContext : DbContext
217225
{
218-
public UserIdValueConverter(ConverterMappingHints? mappingHints = null)
219-
: base(id => id.Value, value => new(value), mappingHints) { }
226+
public DbSet<User> Users => Set<User>();
227+
public DbSet<Order> Orders => Set<Order>();
228+
229+
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
230+
{
231+
// Register once - applies everywhere these types appear
232+
configurationBuilder.Properties<UserId>()
233+
.HaveConversion<StrongOfValueConverter<UserId, Guid>>();
234+
235+
configurationBuilder.Properties<Email>()
236+
.HaveConversion<StrongOfValueConverter<Email, string>>();
237+
238+
configurationBuilder.Properties<Amount>()
239+
.HaveConversion<StrongOfValueConverter<Amount, decimal>>();
240+
}
220241
}
221242
```
222243

223-
There is no benefit in providing you a base class with an additional package and dependency.
244+
### Per-Property Configuration
245+
246+
For explicit control over individual properties (e.g. column constraints):
247+
248+
```csharp
249+
protected override void OnModelCreating(ModelBuilder modelBuilder)
250+
{
251+
modelBuilder.Entity<User>(entity =>
252+
{
253+
entity.Property(e => e.Id)
254+
.HasStrongOfConversion<UserId, Guid>();
255+
256+
entity.Property(e => e.Email)
257+
.HasStrongOfConversion<Email, string>()
258+
.HasMaxLength(256);
259+
});
260+
}
261+
```
262+
263+
### LINQ Limitations
264+
265+
EF Core value converters work transparently for CRUD, equality comparisons (`==`, `!=`), and ordering. However, be aware of these limitations:
266+
267+
- **`.Value` in LINQ** - not translatable to SQL. Compare strong types against strong types instead.
268+
- **`Contains` with mixed types** - the list must be the strong type (`UserId`), not the primitive (`Guid`). Use `UserId.From(guids)` to convert.
269+
- **`ToString()` in LINQ** - not translatable. Format client-side after materializing.
270+
- **Aggregations** - may require casting to the nullable primitive type (e.g. `(decimal?)order.Total`).
271+
272+
See the [StrongOf.EntityFrameworkCore readme](src/StrongOf.EntityFrameworkCore/readme.md) for detailed examples and a full compatibility table.
273+
274+
### Why No Source Generator for EF Core?
275+
276+
The generic `StrongOfValueConverter<TStrong, TTarget>` already eliminates all per-type boilerplate. Combined with `ConfigureConventions`, registration is a single line per type. A source generator would add Roslyn coupling complexity and contradicts the project's design philosophy (see FAQ below) - for zero practical benefit.
224277

225278
## Usage with FluentValidation
226279

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright © Benjamin Abt (https://benjamin-abt.com) - all rights reserved
2+
3+
using BenchmarkDotNet.Attributes;
4+
using BenchmarkDotNet.Configs;
5+
using BenchmarkDotNet.Jobs;
6+
7+
namespace StrongOf.Benchmarks.StrongCollection;
8+
9+
#pragma warning disable CA1822 // Mark members as static
10+
11+
[MemoryDiagnoser]
12+
[SimpleJob(RuntimeMoniker.Net80)]
13+
[SimpleJob(RuntimeMoniker.Net90)]
14+
[SimpleJob(RuntimeMoniker.Net10_0, baseline: true)]
15+
[CategoriesColumn]
16+
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
17+
public class StrongCollectionBenchmark
18+
{
19+
private readonly Guid[] _guids = Enumerable.Range(0, 100).Select(_ => Guid.NewGuid()).ToArray();
20+
private readonly int[] _ints = Enumerable.Range(0, 100).ToArray();
21+
private readonly string[] _strings = Enumerable.Range(0, 100).Select(i => $"value_{i}").ToArray();
22+
23+
[Benchmark]
24+
[BenchmarkCategory("Guid")]
25+
public List<TestStrongGuid>? Guid_From_Array()
26+
=> TestStrongGuid.From(_guids);
27+
28+
[Benchmark]
29+
[BenchmarkCategory("Int32")]
30+
public List<TestStrongInt32>? Int32_From_Array()
31+
=> TestStrongInt32.From(_ints);
32+
33+
[Benchmark]
34+
[BenchmarkCategory("String")]
35+
public List<TestStrongString>? String_From_Array()
36+
=> TestStrongString.From(_strings);
37+
}
38+
39+
// Test Classes
40+
41+
public sealed class TestStrongGuid(Guid Value) : StrongGuid<TestStrongGuid>(Value);
42+
public sealed class TestStrongInt32(int Value) : StrongInt32<TestStrongInt32>(Value);
43+
public sealed class TestStrongString(string Value) : StrongString<TestStrongString>(Value);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright © Benjamin Abt (https://benjamin-abt.com) - all rights reserved
2+
3+
using BenchmarkDotNet.Attributes;
4+
using BenchmarkDotNet.Configs;
5+
using BenchmarkDotNet.Jobs;
6+
7+
namespace StrongOf.Benchmarks.StrongEquality;
8+
9+
#pragma warning disable CA1822 // Mark members as static
10+
11+
[MemoryDiagnoser]
12+
[SimpleJob(RuntimeMoniker.Net80)]
13+
[SimpleJob(RuntimeMoniker.Net90)]
14+
[SimpleJob(RuntimeMoniker.Net10_0, baseline: true)]
15+
[CategoriesColumn]
16+
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
17+
public class StrongEqualityBenchmark
18+
{
19+
private static readonly TestStrongGuid s_guid1 = new(Guid.Parse("550e8400-e29b-41d4-a716-446655440000"));
20+
private static readonly TestStrongGuid s_guid2 = new(Guid.Parse("550e8400-e29b-41d4-a716-446655440000"));
21+
private static readonly TestStrongGuid s_guid3 = new(Guid.Parse("660e8400-e29b-41d4-a716-446655440000"));
22+
23+
private static readonly TestStrongString s_str1 = new("Batman");
24+
private static readonly TestStrongString s_str2 = new("Batman");
25+
private static readonly TestStrongString s_str3 = new("Robin");
26+
27+
private static readonly TestStrongInt32 s_int1 = new(42);
28+
private static readonly TestStrongInt32 s_int2 = new(42);
29+
30+
[Benchmark]
31+
[BenchmarkCategory("Guid")]
32+
public bool Guid_Equals_Typed()
33+
=> s_guid1.Equals(s_guid2);
34+
35+
[Benchmark]
36+
[BenchmarkCategory("Guid")]
37+
public bool Guid_Equals_Object()
38+
=> s_guid1.Equals((object)s_guid2);
39+
40+
[Benchmark]
41+
[BenchmarkCategory("Guid")]
42+
public bool Guid_NotEqual()
43+
=> s_guid1 != s_guid3;
44+
45+
[Benchmark]
46+
[BenchmarkCategory("String")]
47+
public bool String_Equals_Typed()
48+
=> s_str1.Equals(s_str2);
49+
50+
[Benchmark]
51+
[BenchmarkCategory("String")]
52+
public bool String_NotEqual()
53+
=> s_str1 != s_str3;
54+
55+
[Benchmark]
56+
[BenchmarkCategory("Int32")]
57+
public bool Int32_Equals_Typed()
58+
=> s_int1.Equals(s_int2);
59+
60+
[Benchmark]
61+
[BenchmarkCategory("Int32")]
62+
public int Int32_CompareTo()
63+
=> s_int1.CompareTo(s_int2);
64+
}
65+
66+
// Test Classes
67+
68+
public sealed class TestStrongGuid(Guid Value) : StrongGuid<TestStrongGuid>(Value);
69+
public sealed class TestStrongString(string Value) : StrongString<TestStrongString>(Value);
70+
public sealed class TestStrongInt32(int Value) : StrongInt32<TestStrongInt32>(Value);

0 commit comments

Comments
 (0)