Skip to content

Commit

Permalink
Merge pull request #23 from BenjaminAbt/feature/add-net7-and-docs
Browse files Browse the repository at this point in the history
add .NET and add new .NET 9 Benchmark
  • Loading branch information
BenjaminAbt authored Nov 15, 2024
2 parents d50fc41 + 2fce0a5 commit 3374ff2
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 52 deletions.
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</PropertyGroup>

<PropertyGroup Label="Project Defaults">
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Label="Package">
Expand All @@ -42,7 +42,7 @@
</PropertyGroup>

<PropertyGroup Label="C#">
<LangVersion>12.0</LangVersion>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand Down
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup Label=".NET">
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
<PackageVersion Include="FluentValidation" Version="11.10.0" />
</ItemGroup>
Expand All @@ -14,7 +14,7 @@
</PackageVersion>
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.5.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.177">
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.179">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageVersion>
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 BEN ABT
Copyright (c) 2023-2024 BEN ABT

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
89 changes: 55 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
# StrongOf <a href="https://www.buymeacoffee.com/benjaminabt" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="30" ></a>

StrongOf helps to implement primitives as a strong type that represents a domain object (e.g. UserId, EmailAddress, etc.). It is a simple class that wraps a value and provides a few helper methods to make it easier to work with.
[![Build](https://github.com/benjaminabt/StrongOf/actions/workflows/ci.yml/badge.svg)](https://github.com/benjaminabt/StrongOf/actions/workflows/ci.yml)

In contrast to other approaches, StrongOf is above all simple and performant - and not over-engineered.
||StrongOf|StrongOf.AspNetCore|StrongOf.Json|StrongOf.FluentValidation|
|-|-|-|-|-|
|*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)|

## Why?
All [StrongOf Packages](https://www.nuget.org/packages/StrongOf) are available for .NET 7, .NET 8 and .NET 9.

---

This library was developed because C# did not support [type abbreviations](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/type-abbreviations) up to and including version 12.
__StrongOf__ helps to implement primitives as a strong type that represents a domain object (e.g. UserId, EmailAddress, etc.). It is a simple class that wraps a value and provides a few helper methods to make it easier to work with.

With C# 13 we finally get [Extension Types](https://devblogs.microsoft.com/dotnet/dotnet-build-2024-announcements)!
In contrast to other approaches, __StrongOf__ is above all simple and performant - and not over-engineered.

## Why?

This library was developed because C# did not support [type abbreviations](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/type-abbreviations) up to and including version 12. Originally announced for C#13, we should finally receive [Extension Types](https://devblogs.microsoft.com/dotnet/dotnet-build-2024-announcements) with C# 14.

See GitHub proposal: [Proposal: Type aliases / abbreviations / newtype](https://github.com/dotnet/csharplang/issues/410)

Expand Down Expand Up @@ -182,40 +190,53 @@ public class MySubmitModelValidator : AbstractValidator<MySubmitModel>
```


## Installation

[![StrongOf](https://img.shields.io/nuget/v/StrongOf.svg?logo=nuget&label=StrongOf)](https://www.nuget.org/packages/StrongOf)\
[![StrongOf.AspNetCore](https://img.shields.io/nuget/v/StrongOf.AspNetCore.svg?logo=nuget&label=StrongOf.AspNetCore)](https://www.nuget.org/packages/StrongOf.AspNetCore)\
[![StrongOf.Json](https://img.shields.io/nuget/v/StrongOf.Json.svg?logo=nuget&label=StrongOf.Json)](https://www.nuget.org/packages/StrongOf.Json)\
[![StrongOf.FluentValidation](https://img.shields.io/nuget/v/StrongOf.FluentValidation.svg?logo=nuget&label=StrongOf.FluentValidation)](https://www.nuget.org/packages/StrongOf.FluentValidation)
See [StrongOf on NuGet.org](https://www.nuget.org/packages/StrongOf)
## Performance matters

Since the strong types created here can still be instantiated with `new()`, this also means an enormous performance advantage over libraries that have to work with `Activator.CreateInstance` or `Expression.New`.

```shell
BenchmarkDotNet v0.13.10, Windows 10 (10.0.19045.3803/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100
[Host] : .NET 7.0.14 (7.0.1423.51910), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.14 (7.0.1423.51910), X64 RyuJIT AVX2


| Method | Mean | Error | StdDev | Median | Gen0 | Allocated |
|------------ |----------:|----------:|----------:|----------:|-------:|----------:|
| Guid_New | 3.366 ns | 0.0998 ns | 0.0934 ns | 3.379 ns | 0.0019 | 32 B |
| Guid_From | 12.301 ns | 1.0492 ns | 3.0936 ns | 14.184 ns | 0.0019 | 32 B |
| | | | | | | |
| Int32_New | 2.996 ns | 0.1013 ns | 0.1167 ns | 2.954 ns | 0.0014 | 24 B |
| Int32_From | 9.829 ns | 0.6536 ns | 1.9273 ns | 10.486 ns | 0.0014 | 24 B |
| | | | | | | |
| Int64_New | 2.703 ns | 0.0867 ns | 0.0724 ns | 2.671 ns | 0.0014 | 24 B |
| Int64_From | 11.706 ns | 0.4101 ns | 1.2093 ns | 11.976 ns | 0.0014 | 24 B |
| | | | | | | |
| String_New | 3.807 ns | 0.1205 ns | 0.1127 ns | 3.837 ns | 0.0014 | 24 B |
| String_From | 11.339 ns | 0.4997 ns | 1.4735 ns | 10.969 ns | 0.0014 | 24 B |
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5131/22H2/2022Update)
AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 9.0.100
[Host] : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
.NET 7.0 : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
.NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI


| Method | Runtime | Mean | Ratio |
|------------ |--------- |---------:|------:|
| Guid_New | .NET 7.0 | 1.712 ns | 1.03 |
| Guid_New | .NET 8.0 | 1.672 ns | 1.00 |
| Guid_New | .NET 9.0 | 1.665 ns | 1.00 |
| | | | |
| Guid_From | .NET 7.0 | 3.250 ns | 1.32 |
| Guid_From | .NET 8.0 | 3.165 ns | 1.29 |
| Guid_From | .NET 9.0 | 2.462 ns | 1.00 |
| | | | |
| Int32_New | .NET 7.0 | 1.604 ns | 1.00 |
| Int32_New | .NET 8.0 | 1.637 ns | 1.02 |
| Int32_New | .NET 9.0 | 1.609 ns | 1.00 |
| | | | |
| Int32_From | .NET 7.0 | 2.738 ns | 1.41 |
| Int32_From | .NET 8.0 | 2.636 ns | 1.36 |
| Int32_From | .NET 9.0 | 1.942 ns | 1.00 |
| | | | |
| Int64_New | .NET 7.0 | 1.633 ns | 1.02 |
| Int64_New | .NET 8.0 | 1.605 ns | 1.00 |
| Int64_New | .NET 9.0 | 1.608 ns | 1.00 |
| | | | |
| Int64_From | .NET 7.0 | 2.673 ns | 1.41 |
| Int64_From | .NET 8.0 | 2.648 ns | 1.40 |
| Int64_From | .NET 9.0 | 1.890 ns | 1.00 |
| | | | |
| String_New | .NET 7.0 | 2.613 ns | 1.61 |
| String_New | .NET 8.0 | 1.582 ns | 0.97 |
| String_New | .NET 9.0 | 1.627 ns | 1.00 |
| | | | |
| String_From | .NET 7.0 | 3.526 ns | 1.28 |
| String_From | .NET 8.0 | 3.596 ns | 1.31 |
| String_From | .NET 9.0 | 2.746 ns | 1.00 |
```

For certain scenarios, this library also has an `Expression.New` implementation (through a static From method); but not for general instantiation.
Expand Down
48 changes: 36 additions & 12 deletions perf/StrongOf.Benchmarks/StrongNew/StrongNewBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;

namespace StrongOf.Benchmarks.StrongNew;

public sealed class TestStrongInt32(int Value) : StrongInt32<TestStrongInt32>(Value);
public sealed class TestStrongInt64(long Value) : StrongInt64<TestStrongInt64>(Value);
public sealed class TestStrongString(string Value) : StrongString<TestStrongString>(Value);
public sealed class TestStrongGuid(Guid Value) : StrongGuid<TestStrongGuid>(Value);
#pragma warning disable CA1822 // Mark members as static

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net70)] // PGO enabled by default
[SimpleJob(RuntimeMoniker.Net80)]
[SimpleJob(RuntimeMoniker.Net90, baseline: true)]
[CategoriesColumn]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
public class StrongNewBenchmark
Expand All @@ -17,33 +18,56 @@ public class StrongNewBenchmark

[Benchmark]
[BenchmarkCategory("StrongInt32")]
public TestStrongInt32 Int32_New() => new TestStrongInt32(31);
public TestStrongInt32 Int32_New()
=> new(31);

[Benchmark]
[BenchmarkCategory("StrongInt32")]
public TestStrongInt32 Int32_From() => TestStrongInt32.From(31);
public TestStrongInt32 Int32_From()
=> TestStrongInt32.From(31);

[Benchmark]
[BenchmarkCategory("StrongInt64")]
public TestStrongInt64 Int64_New() => new TestStrongInt64(31);
public TestStrongInt64 Int64_New()
=> new(31);

[Benchmark]
[BenchmarkCategory("StrongInt64")]
public TestStrongInt64 Int64_From() => TestStrongInt64.From(31);
public TestStrongInt64 Int64_From()
=> TestStrongInt64.From(31);

[Benchmark]
[BenchmarkCategory("StrongString")]
public TestStrongString String_New() => new TestStrongString("Batman");
public TestStrongString String_New()
=> new("Batman");

[Benchmark]
[BenchmarkCategory("StrongString")]
public TestStrongString String_From() => TestStrongString.From("Batman");
public TestStrongString String_From()
=> TestStrongString.From("Batman");

[Benchmark]
[BenchmarkCategory("StrongGuid")]
public TestStrongGuid Guid_New() => new TestStrongGuid(s_guid);
public TestStrongGuid Guid_New()
=> new(s_guid);

[Benchmark]
[BenchmarkCategory("StrongGuid")]
public TestStrongGuid Guid_From() => TestStrongGuid.From(s_guid);
public TestStrongGuid Guid_From()
=> TestStrongGuid.From(s_guid);
}


// Test Classes

public sealed class TestStrongInt32(int Value)
: StrongInt32<TestStrongInt32>(Value);

public sealed class TestStrongInt64(long Value)
: StrongInt64<TestStrongInt64>(Value);

public sealed class TestStrongString(string Value)
: StrongString<TestStrongString>(Value);

public sealed class TestStrongGuid(Guid Value)
: StrongGuid<TestStrongGuid>(Value);
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.1",
"version": "1.2",
"assemblyVersion": {
"precision": "revision" // optional. Use when you want a more precise assembly version than the default major.minor.
},
Expand Down

0 comments on commit 3374ff2

Please sign in to comment.