Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
UsingStringNatively 151.8 ns 32.19 1.76 1.00 0.00 0.0153 256 B 1.00
UsingValueObjectAsStruct 184.8 ns 12.19 0.67 1.22 0.02 0.0153 256 B 1.00
There’s a minor performance overhead, but these measurements are incredibly small. Also, there’s no memory overhead.
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22621.1194)
          AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
          .NET SDK=7.0.102
          [Host]   : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
          ShortRun : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
          Job=ShortRun  IterationCount=3  LaunchCount=1
          WarmupCount=3
          
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated
UsingIntNatively 14.55 ns 1.443 ns 0.079 ns 1.00 0.00 - -
UsingValueObjectStruct 14.88 ns 3.639 ns 0.199 ns 1.02 0.02 - -
There is no discernible difference between using a native int and a VO struct; both are pretty much the same in terms of speed and memory.
The next most common scenario is using a VO class to represent a native String. These results are:
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22621.1194)
          AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
          .NET SDK=7.0.102
          
          ``` ini
          BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22621.1194)
          AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
          .NET SDK=7.0.102
          [Host]   : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
          ShortRun : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
          Job=ShortRun  IterationCount=3  LaunchCount=1
          WarmupCount=3
          

(to run these yourself: dotnet run -c Release --framework net8.0 -- --job short --filter * in the Vogen.Benchmarks folder)
As mentioned previously, the goal of Vogen is to achieve similar performance compared to using primitives themselves. Here’s a benchmark comparing a validated Value Object with an underlying type of int vs. using an int natively (primitively 🤓)

|                  Method |     Mean |    Error |   StdDev | Ratio | Allocated |
          |------------------------ |---------:|---------:|---------:|------:|----------:|
          |        UsingIntNatively | 17.04 ns | 0.253 ns | 0.014 ns |  1.00 |         - |
          |  UsingValueObjectStruct | 19.76 ns | 2.463 ns | 0.135 ns |  1.16 |         - |  
          There's hardly any speed overhead, and no memory overhead.  
          The next most common scenario is using a VO to represent a string:  
          |                   Method |     Mean |    Error |  StdDev | Ratio | Allocated |
          |------------------------- |---------:|---------:|--------:|------:|----------:|
          |      UsingStringNatively | 204.4 ns |  8.09 ns | 0.44 ns |  1.00 |     256 B |
          

(to run these yourself: dotnet run -c Release --framework net8.0 -- --job short --filter * in the Vogen.Benchmarks folder)
As mentioned previously, the goal of Vogen is to achieve very similar performance compare to using primitives themselves. Here’s a benchmark comparing the use of a validated value object with underlying type of int vs using an int natively (primitively 🤓)

// --- catches argument / parameter expressions
          
          // error VOG010: Type 'CustomerId' cannot be constructed with 'new'
          Task t = Task.FromResult(new());
          
          // error VOG009: Type 'CustomerId' cannot be constructed with default
          void Process(CustomerId customerId = default) { }
          

One of the main goals of Vogen is to achieve almost the same speed and memory performance as using primitives directly. Put another way, if your decimal primitive represents an Account Balance, then there is extremely low overhead of using an AccountBalance Value Object instead. Please see the performance metrics for more information.

void Process(CustomerId customerId = default) { } // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
          

One of the main goals of this project is to achieve almost the same speed and memory performance as using primitives directly. Put another way, if your decimal primitive represents an Account Balance, then there is extremely low overhead of using an AccountBalance value object instead. Please see the performance metrics below.


Top-Level Directory Explanations

samples/ - This directory contains example projects that demonstrate the usage of the Vogen library. Each subdirectory represents a different example, and contains the necessary files and configurations for that example.

src/ - This directory contains the source code for the project. It includes the Vogen library itself, as well as any shared types and code fixers.

src/obj/ - This directory contains object files generated during the compilation process.

src/Vogen/ - This subdirectory contains the core Vogen library code. It includes subdirectories for diagnostics, extensions, generators, properties, rules, suppressors, templates, and binaries and object files.

tests/ - This directory contains unit tests and benchmarks for the project. It includes subdirectories for analyzer tests, consumer tests, snapshot tests, and Vogen benchmarks.

tests/AnalyzerTests/ - This subdirectory contains unit tests for the analyzer component of the Vogen library.

tests/ConsumerTests/ - This subdirectory contains unit tests for the consumer-side components of the Vogen library.

tests/SnapshotTests/ - This subdirectory contains snapshot tests, which test the output of the code generation and serialization components of the Vogen library.

tests/Vogen.Benchmarks/ - This subdirectory contains benchmarks for the performance of the Vogen library.