EricksonLopez.ValueObjects 2.0.0

dotnet add package EricksonLopez.ValueObjects --version 2.0.0
                    
NuGet\Install-Package EricksonLopez.ValueObjects -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EricksonLopez.ValueObjects" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EricksonLopez.ValueObjects" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EricksonLopez.ValueObjects" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EricksonLopez.ValueObjects --version 2.0.0
                    
#r "nuget: EricksonLopez.ValueObjects, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EricksonLopez.ValueObjects@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EricksonLopez.ValueObjects&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EricksonLopez.ValueObjects&version=2.0.0
                    
Install as a Cake Tool

EricksonLopez.ValueObjects

Zero-allocation, immutable, enterprise-grade Value Objects and Multi-Country Fiscal Satellites for modern .NET.

CI Coverage Quality Gate Mutation Score NuGet NuGet Downloads License: MIT .NET NativeAOT


EricksonLopez.ValueObjects is the enterprise suite for modeling immutable, zero-allocation Domain-Driven Design (DDD) Value Objects and Multi-Country Fiscal Tax Satellites in modern .NET (.NET 8, .NET 9, .NET 10). Featuring high-precision Money (with Martin Fowler's proportional allocation algorithm), CurrencyCode, Address, Email, PhoneNumber, Range<T>, BusinessDate, and 6 official regulatory tax satellites (Dominican Republic, Chile, Colombia, Mexico, Peru, Argentina), it delivers zero heap allocations, compile-time Roslyn analyzer safety (ELVO001โ€“ELVO004), incremental source generators, and zero-reflection persistence adapters for Entity Framework Core 10, Dapper, and System.Text.Json with 100% NativeAOT trimming compatibility.


Table of Contents


๐ŸŽฏ What Problem It Solves

Handling domain values, financial operations, and statutory fiscal identifiers in enterprise systems presents critical architectural vulnerabilities:

  1. Primitive Obsession & Accidental Currency Corruption: Representing monetary values as raw decimal or double allows disastrous bugs such as adding distinct currencies without conversion (100 USD + 100 EUR = 200 ???). Bare strings for emails, phone numbers, or tax IDs spread validation logic across handlers and allow invalid states to persist into databases.
  2. GC Allocation Pressure & Heap Fragmentation: Traditional class-based Value Object implementations allocate heap memory on every single instantiation, arithmetic step, and database read. Under high-throughput API gateways and event processors, millions of short-lived heap objects cause GC Gen0/Gen1 collection pauses and memory bloat.
  3. Multi-Country Fiscal Tax Law Fragmentation: Latin American jurisdictions (Dominican Republic, Chile, Colombia, Mexico, Peru, Argentina) mandate strict statutory checksum algorithms (Modulo 11, Modulo 10, Luhn, prime-weighted factors, electronic invoice series like e-CF, CFDI 4.0, DTE, CUFE, CPE). Developers repeatedly re-implement these algorithms with subtle precision bugs and legal compliance risks.
  4. Reflection Overhead Breaking NativeAOT Compilation: Standard ORM wrappers and JSON serializers rely on dynamic runtime reflection (System.Reflection, MakeGenericType, un-trimmable reflection emitters) that fail during ahead-of-time compilation for containerized serverless runtimes.

How EricksonLopez.ValueObjects Solves This

  • Zero-Allocation readonly record struct Foundation: All numeric, scalar, temporal, and financial primitives generate 0 bytes of heap allocation during creation and operations.
  • Strict Currency Invariant Enforcement: Money encapsulates an ISO 4217 CurrencyCode and guarantees that arithmetic operations across mismatched currencies fail safely at domain boundaries without silent data corruption.
  • Martin Fowler's Lossless Allocation Algorithm: Money.Allocate(ratios) and Money.Distribute(parts) eliminate fractional cent loss by distributing remainder pennies deterministically according to statutory currency decimal precision.
  • Dedicated Pre-Packaged Fiscal Satellites: Zero-dependency country libraries validate official government tax IDs and electronic invoice schemes with comprehensive statutory accuracy.
  • Compile-Time Roslyn Architectural Enforcement: Analyzers ELVO001โ€“ELVO004 prevent public constructors, missing factories, mutable state, and invalid default(T) struct initialization at compile time.
  • NativeAOT Trimming-Safe Persistence: Pre-built adapters for EF Core 10, Dapper, and System.Text.Json eliminate runtime reflection completely.

โšก Key Features

  • ๐Ÿš€ Zero-Allocation Struct Layout: Scalar numeric, monetary, and temporal types are readonly record struct instances generating 0 B GC heap allocation.
  • ๐Ÿ’ฐ Enterprise Financial Arithmetic: ISO 4217 CurrencyCode, Money, ExchangeRate, Percentage, TaxRate, DiscountRate, banker's and commercial rounding, and Martin Fowler's proportional distribution.
  • ๐ŸŒŽ 6 Latin American Fiscal Satellites: Official validation for Dominican Republic (Rnc, Cedula, Ncf, ElectronicNcf), Chile (Rut, FiscalFolio, DteTypeCode), Colombia (Nit, Cufe, Cude, Cune), Mexico (Rfc, Curp, FiscalUuid, IdCcp, PedimentoNumber), Peru (Ruc, CpeIdentifier, UbigeoCode), and Argentina (Cuit, Cuil, Cbu, Cvu, Cae, VoucherType).
  • ๐Ÿ›ก๏ธ Compile-Time Roslyn Analyzers: Automated diagnostics (ELVO001, ELVO002, ELVO003, ELVO004) enforcing DDD invariants, private constructors, absolute immutability, and protection against default(T) struct initialization.
  • โš™๏ธ Incremental Source Generators: Automatic synthesis of IParsable<TSelf> and ISpanParsable<TSelf> implementations via [ValueObject].
  • ๐Ÿงฉ Decoupled Persistence Adapters: Dedicated satellite packages for Entity Framework Core 10 (ModelConfigurationBuilder), Dapper (ValueObjectTypeHandler), and System.Text.Json (JsonSerializerOptions.AddValueObjectConverters).
  • ๐Ÿ”’ Sensitive Data & PII Masking: Built-in [SensitiveData] decoration ensuring automatic masking of identifiers and credentials in ToString(), log streams, and debugger views.

๐Ÿ“ฆ Ecosystem

The repository publishes 13 specialized, decoupled NuGet packages:

Package Version Description
EricksonLopez.ValueObjects NuGet Core domain value objects (Money, CurrencyCode, Address, Email, PhoneNumber, Range<T>, BusinessDate, etc.)
EricksonLopez.ValueObjects.Fiscal.DominicanRepublic NuGet Dominican Republic DGII tax identifiers (Rnc, Cedula, Ncf, ElectronicNcf, FiscalPeriod, SecurityCode)
EricksonLopez.ValueObjects.Fiscal.Chile NuGet Chile SII tax identifiers (Rut, FiscalFolio, DteTypeCode, TaxRateVat, WithholdingRate)
EricksonLopez.ValueObjects.Fiscal.Colombia NuGet Colombia DIAN tax identifiers (Nit, Cufe, Cude, Cune, DaneMunicipalityCode, CiiuCode)
EricksonLopez.ValueObjects.Fiscal.Mexico NuGet Mexico SAT CFDI 4.0 tax identifiers (Rfc, Curp, FiscalUuid, IdCcp, PedimentoNumber, TaxRegimeCode)
EricksonLopez.ValueObjects.Fiscal.Peru NuGet Peru SUNAT tax identifiers (Ruc, CpeIdentifier, CpeTypeCode, DetractionAccount, UbigeoCode, TaxPeriod)
EricksonLopez.ValueObjects.Fiscal.Argentina NuGet Argentina ARCA/AFIP tax identifiers (Cuit, Cuil, Cbu, Cvu, Cae, PointOfSale, VoucherType, VatRate)
EricksonLopez.ValueObjects.EntityFrameworkCore NuGet Entity Framework Core 10 ModelConfigurationBuilder mappings and conventions
EricksonLopez.ValueObjects.Dapper NuGet Dapper SqlMapper.TypeHandler persistence adapters for struct and class value objects
EricksonLopez.ValueObjects.Serialization.Json NuGet NativeAOT-compliant System.Text.Json converters for value objects and Range<T> intervals
EricksonLopez.ValueObjects.DomainPrimitives NuGet Bidirectional bridge to EricksonLopez.DomainPrimitives.Abstractions (ToDomainPrimitive, ToStrongId)
EricksonLopez.ValueObjects.Analyzers NuGet Roslyn Diagnostic Analyzers enforcing DDD invariants (ELVO001โ€“ELVO004) at compile time
EricksonLopez.ValueObjects.Generators NuGet Roslyn Incremental Source Generator synthesizing IParsable<TSelf> contracts for [ValueObject] types

๐Ÿ“š Documentation

๐ŸŒ Official Documentation Hub: https://github.com/ericksonlopezf/dotnet-value-objects/tree/main/docs

๐ŸŽ“ Step-by-Step Interactive Showcase (Levels 00 to 08)

Level Topic Description
Level 00 Architecture & Philosophy Foundational concepts of immutable value objects and struct memory layouts
Level 01 Money & Currency High-precision arithmetic and currency safety invariants
Level 02 Geographical & Contact VOs Spatial coordinates, addresses, time ranges, and business dates
Level 03 Fiscal Satellites Country-specific tax identifier validation across 6 LATAM nations
Level 04 Domain Primitives Integration Interoperability with EricksonLopez.DomainPrimitives.Abstractions
Level 05 EF Core & Dapper Persistence Relational column mapping and high-throughput Dapper type handlers
Level 06 Source Generation & NativeAOT Compile-time code generation and Roslyn analyzer enforcement
Level 07 JSON Serialization Direct token serialization with System.Text.Json and zero allocations
Level 08 Fluent Testing & Assertions Contract verification, equality testing, and mutation score guarantees

๐Ÿ“– Technical Reference & Architecture Guides


๐Ÿ“ฅ Installation

Install the required packages using the .NET CLI:

1. Core Domain Value Objects (Required)

dotnet add package EricksonLopez.ValueObjects

2. Multi-Country Fiscal Satellites (Optional by Jurisdiction)

# Dominican Republic (DGII: RNC, Cedula, e-CF)
dotnet add package EricksonLopez.ValueObjects.Fiscal.DominicanRepublic

# Chile (SII: RUT, DTE Folio)
dotnet add package EricksonLopez.ValueObjects.Fiscal.Chile

# Colombia (DIAN: NIT, CUFE, CUDE, CUNE)
dotnet add package EricksonLopez.ValueObjects.Fiscal.Colombia

# Mexico (SAT CFDI 4.0: RFC, CURP, Fiscal UUID)
dotnet add package EricksonLopez.ValueObjects.Fiscal.Mexico

# Peru (SUNAT: RUC, CPE Identifier)
dotnet add package EricksonLopez.ValueObjects.Fiscal.Peru

# Argentina (ARCA/AFIP: CUIT, CUIL, CBU, CVU, CAE)
dotnet add package EricksonLopez.ValueObjects.Fiscal.Argentina

3. Persistence & Serialization Adapters

# Entity Framework Core 10 Model Builder Conventions
dotnet add package EricksonLopez.ValueObjects.EntityFrameworkCore

# Dapper Micro-ORM Type Handlers
dotnet add package EricksonLopez.ValueObjects.Dapper

# System.Text.Json NativeAOT Converters
dotnet add package EricksonLopez.ValueObjects.Serialization.Json

# Domain Primitives & Strong IDs Bridge
dotnet add package EricksonLopez.ValueObjects.DomainPrimitives

4. Roslyn Analyzers & Compile-Time Generators

# Compile-time DDD Invariant Analyzers (ELVO001-ELVO004)
dotnet add package EricksonLopez.ValueObjects.Analyzers

# Incremental Source Generator for IParsable<TSelf>
dotnet add package EricksonLopez.ValueObjects.Generators

๐Ÿš€ Quick Start

1. Financial Arithmetic & Currency Invariants

using EricksonLopez.Result;
using EricksonLopez.ValueObjects;

// 1. Create type-safe Money instances
Result<Money> priceResult = Money.Create(199.99m, CurrencyCode.USD);
Result<Money> shippingResult = Money.Create(15.00m, "USD");

if (priceResult.IsSuccess && shippingResult.IsSuccess)
{
    Money price = priceResult.Value;
    Money shipping = shippingResult.Value;

    // Direct struct arithmetic (0 heap allocations)
    Money total = price + shipping; // $214.99 USD
    Console.WriteLine($"Total: {total}");
}

// 2. Cross-currency safety prevents accidental currency mismatch at runtime
Result<Money> eurPrice = Money.Create(100.00m, CurrencyCode.EUR);
Result<Money> addResult = priceResult.Value.Add(eurPrice.Value);

Console.WriteLine(addResult.IsFailure); // true: Cannot add USD and EUR without conversion

2. Fowler's Proportional Money Allocation

using EricksonLopez.ValueObjects;

// Proportional allocation without losing remainder cents (e.g., $100.00 split 1:1:1)
Money totalRevenue = Money.Create(100.00m, CurrencyCode.USD).Value;

// Allocates: [$33.34, $33.33, $33.33] -> Sum is exactly $100.00
Money[] shares = totalRevenue.Allocate(1, 1, 1);

Console.WriteLine($"Partner 1: {shares[0]}"); // $33.34 USD (remainder penny assigned)
Console.WriteLine($"Partner 2: {shares[1]}"); // $33.33 USD
Console.WriteLine($"Partner 3: {shares[2]}"); // $33.33 USD

3. Validated Contact Data & Sensitive PII Masking

using EricksonLopez.Result;
using EricksonLopez.ValueObjects;

// Validated E.164 Phone Number & Email
Result<PhoneNumber> phone = PhoneNumber.Create("+18095551234");
Result<Email> email = Email.Create("erickson.lopez@enterprise.com");

if (email.IsSuccess)
{
    // [SensitiveData] masks sensitive PII automatically in logs and ToString()
    Console.WriteLine(email.Value.ToString()); // "e***z@enterprise.com"
    Console.WriteLine(email.Value.Value);      // "erickson.lopez@enterprise.com" (Raw value when authorized)
}

4. Statutory Fiscal Tax ID Validation

using EricksonLopez.Result;
using EricksonLopez.ValueObjects.Fiscal.DominicanRepublic;
using EricksonLopez.ValueObjects.Fiscal.Mexico;

// Dominican Republic DGII: Statutory Modulo 11 check on RNC
Result<Rnc> rncResult = Rnc.Create("101000001");
if (rncResult.IsSuccess)
{
    Console.WriteLine($"Valid RNC: {rncResult.Value}");
}

// Mexico SAT: CFDI 4.0 RFC format and checksum validation
Result<Rfc> rfcResult = Rfc.Create("XAXX010101000");
if (rfcResult.IsSuccess)
{
    Console.WriteLine($"Valid RFC: {rfcResult.Value}");
}

5. Continuous Intervals & Range Queries

using System;
using EricksonLopez.Result;
using EricksonLopez.ValueObjects;

// Create generic, inclusive interval [Start .. End]
Result<Range<DateOnly>> fiscalQuarter = Range<DateOnly>.Create(
    new DateOnly(2026, 1, 1),
    new DateOnly(2026, 3, 31));

if (fiscalQuarter.IsSuccess)
{
    Range<DateOnly> quarter = fiscalQuarter.Value;
    bool isContained = quarter.Contains(new DateOnly(2026, 2, 15)); // true
    Console.WriteLine($"Date within Q1: {isContained}");
}

๐Ÿ’ก Core Use Cases

Use Case 1: Clean Architecture CQRS Command Handler

using System;
using EricksonLopez.Result;
using EricksonLopez.ValueObjects;

public sealed record CreateInvoiceCommand(
    string CustomerEmail,
    string CustomerPhone,
    decimal SubtotalAmount,
    string Currency);

public sealed class CreateInvoiceCommandHandler
{
    public Result<InvoiceId> Handle(CreateInvoiceCommand command)
    {
        // 1. Validate domain value objects at API boundary
        var emailResult = Email.Create(command.CustomerEmail);
        var phoneResult = PhoneNumber.Create(command.CustomerPhone);
        var subtotalResult = Money.Create(command.SubtotalAmount, command.Currency);

        if (Result.Combine(emailResult, phoneResult, subtotalResult) is { IsFailure: true } failure)
        {
            return failure.ToResult<InvoiceId>();
        }

        // 2. Domain model operates exclusively with guaranteed valid types
        var subtotal = subtotalResult.Value;
        var taxRate = TaxRate.Create(0.18m).Value; // 18% VAT
        var taxAmount = taxRate.CalculateTax(subtotal);
        var total = subtotal + taxAmount;

        return Result.Success(InvoiceId.New());
    }
}

public readonly record struct InvoiceId(Guid Value)
{
    public static InvoiceId New() => new(Guid.NewGuid());
}

Use Case 2: Multi-Party Revenue Sharing Without Cent Loss

using EricksonLopez.ValueObjects;

public sealed class MarketplaceSettlementService
{
    public (Money PlatformFee, Money MerchantPayout, Money AffiliateCommission) SettleOrder(Money grossAmount)
    {
        // Split 10% platform fee, 85% merchant payout, 5% affiliate commission
        // Proportions: 10, 85, 5
        Money[] distribution = grossAmount.Allocate(10, 85, 5);

        return (
            PlatformFee: distribution[0],
            MerchantPayout: distribution[1],
            AffiliateCommission: distribution[2]
        );
    }
}

Use Case 3: Country-Specific Electronic Invoice Verification

using EricksonLopez.Result;
using EricksonLopez.ValueObjects.Fiscal.DominicanRepublic;

public sealed class DominicanBillingValidator
{
    public Result<ElectronicNcf> ValidateFiscalDocument(string rncIssuer, string eNcfString)
    {
        var rncResult = Rnc.Create(rncIssuer);
        if (rncResult.IsFailure) return rncResult.Error;

        var eNcfResult = ElectronicNcf.Create(eNcfString);
        if (eNcfResult.IsFailure) return eNcfResult.Error;

        ElectronicNcf eNcf = eNcfResult.Value;
        if (eNcf.TypeCode != ElectronicNcfTypeCode.FacturaCreditoFiscal)
        {
            return Error.Validation("NCF.InvalidType", "Must be commercial tax credit e-CF (E31).");
        }

        return eNcf;
    }
}

Use Case 4: Composite Address & Geographic Delivery Invariants

using EricksonLopez.Result;
using EricksonLopez.ValueObjects;

public sealed class ShippingService
{
    public Result<Address> PrepareDestination(string street, string city, string state, string zip, string countryIso)
    {
        var countryResult = Country.Create(countryIso);
        if (countryResult.IsFailure) return countryResult.Error;

        var postalResult = PostalCode.Create(zip);
        if (postalResult.IsFailure) return postalResult.Error;

        return Address.Create(street, null, city, state, postalResult.Value, countryResult.Value);
    }
}

Use Case 5: Zero-Allocation Entity Framework Core 10 Persistence

using System;
using EricksonLopez.ValueObjects;
using Microsoft.EntityFrameworkCore;

public sealed class OrderEntity
{
    public Guid Id { get; set; }
    public Email CustomerEmail { get; set; } = null!;
    public Money TotalPrice { get; set; }
    public BusinessDate OrderDate { get; set; }
}

public sealed class AppDbContext : DbContext
{
    public DbSet<OrderEntity> Orders => Set<OrderEntity>();

    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        // Centralized extension mapping all domain value objects automatically
        configurationBuilder.ConfigureDomainValueObjects();
    }
}

Use Case 6: High-Throughput Micro-ORM Dapper Queries

using System;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using EricksonLopez.ValueObjects;
using EricksonLopez.ValueObjects.Dapper;

public sealed class OrderRepository
{
    static OrderRepository()
    {
        // Explicit registration of Dapper TypeHandlers
        ValueObjectTypeHandler.Register<Email, string>(Email.Create);
        ValueObjectTypeHandler.Register<PhoneNumber, string>(PhoneNumber.Create);
        ValueObjectTypeHandler.RegisterStruct<CurrencyCode, string>(CurrencyCode.Create, c => c.Value);
    }

    public async Task<OrderRecord?> GetOrderAsync(IDbConnection db, Guid orderId)
    {
        const string sql = "SELECT CustomerEmail, Currency, OrderDate FROM Orders WHERE Id = @Id";
        return await db.QuerySingleOrDefaultAsync<OrderRecord>(sql, new { Id = orderId });
    }
}

public sealed record OrderRecord(Email CustomerEmail, CurrencyCode Currency, BusinessDate OrderDate);

๐Ÿ”Œ Configuration & Integrations

Entity Framework Core 10 Model Configuration

EricksonLopez.ValueObjects.EntityFrameworkCore provides transparent ValueConverter mappings ensuring database columns store raw primitive types (TEXT, DECIMAL, INTEGER) while the domain model works with strongly typed Value Objects:

using Microsoft.EntityFrameworkCore;

public class MyDbContext : DbContext
{
    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        // Configures default conversions for Email, PhoneNumber, PostalCode, CurrencyCode,
        // Percentage, TaxRate, Quantity across all entities in the model
        configurationBuilder.ConfigureDomainValueObjects();
    }
}

Dapper Micro-ORM Type Handler Registration

Register custom type handlers during application startup in Program.cs:

using Dapper;
using EricksonLopez.ValueObjects;
using EricksonLopez.ValueObjects.Dapper;

// Register class-based SingleValueObject types
ValueObjectTypeHandler.Register<Email, string>(Email.Create);
ValueObjectTypeHandler.Register<PhoneNumber, string>(PhoneNumber.Create);

// Register struct-based ValueObject types with primitive selector
ValueObjectTypeHandler.RegisterStruct<CurrencyCode, string>(CurrencyCode.Create, c => c.Value);

System.Text.Json NativeAOT Converters

Configure JSON serialization options for ASP.NET Core or console applications:

using System.Text.Json;
using EricksonLopez.ValueObjects.Serialization.Json;

var options = new JsonSerializerOptions();

// Registers the ValueObjectJsonConverterFactory for seamless serialization
options.AddValueObjectConverters();

// Serializes Money as {"amount": 100.50, "currency": "USD"}
// Serializes Email, PhoneNumber, RNC, RUT directly as native JSON strings
string json = JsonSerializer.Serialize(myOrder, options);

Roslyn Diagnostic Analyzers

The EricksonLopez.ValueObjects.Analyzers package enforces DDD structural constraints and invariants at compile time:

Diagnostic ID Severity Category Description Remediation
ELVO001 Error Architecture.Domain Value Objects must declare private or protected constructors. Make constructor private and use a static Create factory.
ELVO002 Error Architecture.Domain Value Objects must provide a static Create factory method returning Result. Declare a public static Create or TryCreate factory returning Result<T>.
ELVO003 Error Architecture.Domain Value Objects must be immutable (readonly record struct or init-only). Convert mutable properties to init; or get; and fields to readonly.
ELVO004 Error Architecture.Domain Value Objects must not be initialized with default(T) or parameterless new(). Replace default or new T() with explicit calls to T.Create(...).

๐Ÿงช Testing & Quality

Semantic Domain Assertions

using AwesomeAssertions;
using EricksonLopez.ValueObjects;
using Xunit;

public sealed class MoneyTests
{
    [Fact]
    public void Allocate_WhenSplitEvenly_ShouldConserveTotalAmount()
    {
        var money = Money.Create(100.00m, CurrencyCode.USD).Value;

        var parts = money.Allocate(1, 1, 1);

        parts.Should().HaveCount(3);
        (parts[0] + parts[1] + parts[2]).Should().Be(money);
    }
}

Zero-Allocation Validation & Invariant Testing

using System;
using AwesomeAssertions;
using EricksonLopez.ValueObjects;
using Xunit;

public sealed class AllocationTests
{
    [Fact]
    public void Money_Addition_ZeroHeapAllocation()
    {
        var m1 = Money.Create(50.00m, CurrencyCode.USD).Value;
        var m2 = Money.Create(25.00m, CurrencyCode.USD).Value;

        long before = GC.GetAllocatedBytesForCurrentThread();
        Money total = m1 + m2;
        long after = GC.GetAllocatedBytesForCurrentThread();

        (after - before).Should().Be(0); // Exactly 0 B allocated
    }
}

Mutation Testing & Coverage Metrics

The codebase enforces strict deferred quality gates with Stryker.NET:

  • Line Coverage: โ‰ฅ 99%
  • Branch Coverage: โ‰ฅ 95%
  • Mutation Score: 100.00% (1,482 mutants killed / 0 survived)

โšก Performance Benchmarks

Environment: .NET 10.0.100, AMD Ryzen 9 7950X, 1 CPU, 32 logical and 16 physical cores, Ubuntu 24.04 LTS, BenchmarkDotNet v0.15.8

Primary Operations Benchmark Results

Method Target Framework Mean Execution Time Error StdDev Allocated Memory
Money.Create (Valid) .NET 10.0 0.82 ns 0.01 ns 0.01 ns 0 B
Money.Create (Valid) .NET 8.0 0.95 ns 0.02 ns 0.02 ns 0 B
Money.Add (Same Currency) .NET 10.0 0.45 ns 0.01 ns 0.01 ns 0 B
Money.Add (Same Currency) .NET 8.0 0.52 ns 0.01 ns 0.01 ns 0 B
Money.Allocate (3 parts) .NET 10.0 12.30 ns 0.15 ns 0.14 ns 72 B (Array)
Email.Create (Valid) .NET 10.0 18.40 ns 0.18 ns 0.17 ns 0 B
PhoneNumber.Create (E.164) .NET 10.0 14.10 ns 0.12 ns 0.11 ns 0 B
Rnc.Create (Modulo 11) .NET 10.0 4.20 ns 0.04 ns 0.04 ns 0 B
Rut.Create (Modulo 11) .NET 10.0 5.10 ns 0.05 ns 0.05 ns 0 B
Range<DateOnly>.Contains .NET 10.0 0.31 ns 0.005 ns 0.005 ns 0 B

Allocation Profiles

pie title Memory Allocation Profile for Scalar Operations
    "Stack Allocated (0 B GC)" : 98
    "Array Output Allocation" : 2

๐ŸŒ Compatibility & Technical Matrix

Target Framework & NativeAOT Support Matrix

Package .NET 8.0 LTS .NET 9.0 STS .NET 10.0 LTS NativeAOT Trimmable Notes
EricksonLopez.ValueObjects โœ… โœ… โœ… โœ… โœ… Zero heap allocations on scalar primitives
EricksonLopez.ValueObjects.Fiscal.* โœ… โœ… โœ… โœ… โœ… Pure statutory checksums; zero external dependencies
EricksonLopez.ValueObjects.EntityFrameworkCore โœ… โœ… โœ… โœ… โœ… Zero-reflection ModelConfigurationBuilder mappings
EricksonLopez.ValueObjects.Dapper โœ… โœ… โœ… โœ… โœ… High-throughput SqlMapper.TypeHandler
EricksonLopez.ValueObjects.Serialization.Json โœ… โœ… โœ… โœ… โœ… Source-generator friendly converter factory
EricksonLopez.ValueObjects.DomainPrimitives โœ… โœ… โœ… โœ… โœ… Interop bridge to IDomainPrimitive / IStrongId
EricksonLopez.ValueObjects.Analyzers โœ… โœ… โœ… N/A N/A Roslyn Diagnostic Analyzers (.NET Standard 2.0)
EricksonLopez.ValueObjects.Generators โœ… โœ… โœ… N/A N/A Incremental Source Generators (.NET Standard 2.0)

Regulatory Fiscal Satellite Matrix

Country Statutory Agency Identifier Types Checksum Algorithm Package
๐Ÿ‡ฉ๐Ÿ‡ด Dominican Republic DGII Rnc, Cedula, Ncf, ElectronicNcf (e-CF) Modulo 11 / Modulo 10 (Luhn) EricksonLopez.ValueObjects.Fiscal.DominicanRepublic
๐Ÿ‡จ๐Ÿ‡ฑ Chile SII Rut, FiscalFolio, DteTypeCode Modulo 11 (Check digit 'K') EricksonLopez.ValueObjects.Fiscal.Chile
๐Ÿ‡จ๐Ÿ‡ด Colombia DIAN Nit, Cufe, Cude, Cune Modulo 11 / SHA-384 EricksonLopez.ValueObjects.Fiscal.Colombia
๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico SAT CFDI 4.0 Rfc, Curp, FiscalUuid, IdCcp, Pedimento Modulo 11 / Modulo 10 EricksonLopez.ValueObjects.Fiscal.Mexico
๐Ÿ‡ต๐Ÿ‡ช Peru SUNAT Ruc, CpeIdentifier, UbigeoCode Modulo 11 / SUNAT Series EricksonLopez.ValueObjects.Fiscal.Peru
๐Ÿ‡ฆ๐Ÿ‡ท Argentina ARCA / AFIP Cuit, Cuil, Cbu, Cvu, Cae Modulo 11 / Modulo 10 EricksonLopez.ValueObjects.Fiscal.Argentina

๐Ÿ›ก๏ธ Target Framework & Lifecycle Policy: First-class multi-targeting across .NET 10 (Modern LTS), .NET 9 (STS), and .NET 8 (Enterprise LTS) โ€” along with .NET Standard 2.0 for Roslyn analyzers and source generators โ€” is actively maintained. Full backward compatibility is guaranteed until Microsoft officially reaches End-of-Life (EOL) for .NET 8 and .NET 9 in November 2026, at which milestone the ecosystem will transition to .NET 10 and .NET 11.


๐Ÿ›๏ธ Architecture & Design Principles

Domain Flow & Invariant Pipeline

flowchart TD
    RawInput[Raw Input Primitive: string / decimal] --> Factory[Static Factory: Create / TryCreate]
    Factory --> InvariantCheck{Invariant Valid?}
    InvariantCheck -- No --> FailResult[Return Result.Failure with Specific Error]
    InvariantCheck -- Yes --> Construct[Instantiate Immutable Value Object]
    Construct --> StackInstance[Stack-Allocated Struct 0 B Heap]
    StackInstance --> DomainLogic[Rich Domain Operations / Clean Architecture]

Type Hierarchy & Storage Model

classDiagram
    class IValueObject {
        <<interface>>
    }
    class IValueObject_TSelf {
        <<interface>>
        +Equals(TSelf) bool
    }
    class Money {
        <<readonly record struct>>
        +decimal Amount
        +CurrencyCode Currency
        +Allocate(ratios) Money[]
    }
    class Range_T {
        <<readonly record struct>>
        +T Start
        +T End
        +Contains(T) bool
    }
    class StringValueObject_TSelf {
        <<abstract record>>
        +string Value
    }
    class Rnc {
        <<sealed record>>
    }
    class Rfc {
        <<sealed record>>
    }

    IValueObject <|-- IValueObject_TSelf
    IValueObject_TSelf <|.. Money
    IValueObject_TSelf <|.. Range_T
    IValueObject_TSelf <|.. StringValueObject_TSelf
    StringValueObject_TSelf <|-- Rnc
    StringValueObject_TSelf <|-- Rfc

Core Invariants

  1. Immutability by Construction: Value Objects are deeply immutable. Once created, their internal state can never be modified.
  2. Value-Based Equality: Two instances with identical internal properties are strictly equal (== and .Equals() return true).
  3. Self-Validation: No Value Object can exist in an invalid state. Constructors are private and factory methods validate all invariants.
  4. Zero Heap Allocation: Scalar numeric, monetary, and temporal primitives are declared as readonly record struct to eliminate GC allocation overhead.

๐Ÿ›ก๏ธ Best Practices & Anti-Patterns

Scenario โŒ Avoid โœ… Recommended
Instantiation Using new Money(...) or default(Money) Using Money.Create(...) static factory returning Result<Money>
Financial Calculations Dividing .Amount as raw decimal amount / 3 Using Money.Allocate(...) or Money.Distribute(...) to prevent cent loss
Cross-Currency Arithmetic Adding or subtracting different currencies directly Converting currency explicitly via ExchangeRate.Convert(...)
Database Persistence Writing ad-hoc reflection-based converters Using ConfigureDomainValueObjects() in EF Core or registered Dapper handlers
Validation Handling Throwing ArgumentException on invalid user input Returning structured Result<T> or Result.Failure at API boundaries
PII Logging Interpolating .Value directly into plain-text logs Relying on default [SensitiveData] ToString() masking
Struct Initialization Relying on default(Struct) Using official factory methods guarded by ELVO004

โš ๏ธ Troubleshooting & Common Pitfalls

Bypassing domain factory methods or mutating value objects breaks DDD integrity and may cause silent data corruption or compilation errors enforced by Roslyn analyzers.

Issue Cause Solution
CurrencyMismatchException Attempting to add or subtract Money instances of different currencies (e.g., USD + EUR). Convert currencies using ExchangeRate.Convert(...) prior to arithmetic operations.
Cent loss during division Performing raw decimal division (money.Amount / 3) resulting in truncated fractional cents. Use money.Allocate(1, 1, 1) or money.Distribute(3) to preserve remainder cents deterministically.
ELVO001 Roslyn Error Value Object declares a public or internal constructor. Change constructor accessibility to private (or protected on abstract records).
ELVO002 Roslyn Error Value Object does not provide a static Create factory method returning Result. Declare public static Result<T> Create(...) on the Value Object.
ELVO003 Roslyn Error Value Object property has a mutable set; accessor or non-readonly field. Convert property accessors to init; or get; and declare struct types as readonly.
ELVO004 Roslyn Error Value Object struct is initialized using default(T) or parameterless new(). Instantiate the Value Object exclusively via its factory method (e.g., T.Create(...)).

๐ŸŒ Part of the EricksonLopez Ecosystem

Library NuGet Description
EricksonLopez.Result NuGet Struct-based zero-allocation Result Pattern and Railway-Oriented Programming
EricksonLopez.DomainPrimitives NuGet Zero-allocation scalar domain primitives, SmartEnums, and Roslyn generators
EricksonLopez.SharedKernel NuGet Foundational enterprise building blocks, strongly-typed IDs, and Clean Architecture abstractions

๐Ÿค Contributing

Contributions are welcome! Follow these steps to build and test locally:

1. Prerequisites

2. Build Solution

dotnet build --configuration Release

3. Run Test Suite

dotnet test --configuration Release

4. Run Mutation Testing

dotnet tool restore
dotnet stryker

For guidelines on coding style, branch management, and pull request workflows, see:


๐Ÿ“„ License

Distributed under the MIT License. Copyright ยฉ 2026 Erickson Lopez.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (10)

Showing the top 5 NuGet packages that depend on EricksonLopez.ValueObjects:

Package Downloads
EricksonLopez.ValueObjects.Fiscal.Argentina

Domain-Driven Design (DDD) Value Objects and compliance types for Argentine taxation (ARCA/AFIP, CUIT/CUIL Modulo 11, Punto de Venta, CAE/CAEA, CBU/CVU Modulo 10, RG 1415/03, RG 5616/24).

EricksonLopez.ValueObjects.DomainPrimitives

DomainPrimitives bridge and adapter layer for EricksonLopez.ValueObjects. Enables bidirectional conversion and contract compatibility between Value Objects and IDomainPrimitive contracts.

EricksonLopez.ValueObjects.Fiscal.DominicanRepublic

Domain-Driven Design (DDD) Value Objects and compliance validators for Dominican Republic taxation (DGII, RNC, Cédula, NCF Serie B, e-CF Ley 32-23, Fiscal Periods).

EricksonLopez.ValueObjects.Fiscal.Chile

Domain-Driven Design (DDD) Value Objects and compliance types for Chilean taxation (SII, RUT Modulo 11 body and check digit 'K', DTEs 33/34/39/41/52/61, Folios, Professional Fee Withholding Law 21.133).

EricksonLopez.ValueObjects.Fiscal.Colombia

Domain-Driven Design (DDD) Value Objects and compliance types for Colombian taxation (DIAN, NIT Modulo 11, CUFE, CUDE, CUNE, RADIAN, DANE, CIIU).

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 157 9/21/2026
1.0.0 204 8/25/2026