uContract 2.0.0

dotnet add package uContract --version 2.0.0
                    
NuGet\Install-Package uContract -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="uContract" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="uContract" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="uContract" />
                    
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 uContract --version 2.0.0
                    
#r "nuget: uContract, 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 uContract@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=uContract&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=uContract&version=2.0.0
                    
Install as a Cake Tool

uContract.NET

Design by Contract library for .NET 8+

Based on Java uContract 2.0.1

A modern Design by Contract (DBC) library for .NET. This is a direct port of the Java uContract 2.0.1 library with .NET-specific improvements.

Build and Test NuGet .NET License Status


Status

uContract.NET follows Semantic Versioning; the public API is stable as of 1.0.0. Changes between releases are tracked in CHANGELOG.md. The current version is shown by the NuGet badge above.


Table of Contents


Quick Start

Installation

dotnet add package uContract

Basic Usage

using uContract;

public class BankAccount
{
    private decimal _balance;
    private string _owner;

    public BankAccount(string owner, decimal initialBalance)
    {
        // Preconditions: Validate inputs
        Contract.RequireNotNull("Owner", owner);
        Contract.Require("Initial balance non-negative", () => initialBalance >= 0);

        _owner = owner;
        _balance = initialBalance;
    }

    public void Transfer(decimal amount)
    {
        // Precondition: Valid amount
        Contract.Require("Amount positive", () => amount > 0);

        // Capture old state for postcondition
        var oldBalance = Contract.Old(() => _balance);

        // Business logic
        _balance -= amount;

        // Postcondition: Balance decreased correctly
        Contract.Ensure("Balance decreased", () => _balance == oldBalance - amount);
    }

    private void CheckInvariant()
    {
        // Invariant: Balance never negative
        Contract.Invariant("Balance non-negative", () => _balance >= 0);
    }
}

Features

Core Capabilities

  • ✅ Preconditions: Validate method inputs and caller state
  • ✅ Postconditions: Verify method results and state changes
  • ✅ Invariants: Enforce class-level constraints
  • ✅ Check Assertions: Runtime checks for general conditions
  • ✅ State Capture: Old<T>() for comparing before/after states
  • ✅ Field Comparison: EnsureAssignable<T>() with reflection-based validation

Design Philosophy

  • 🚀 Zero overhead when disabled: Lazy evaluation via Func<bool> delegates
  • ⚙️ Runtime configuration: Control contracts via environment variables
  • 🎯 DDD-focused: Designed for domain models and aggregate roots
  • 📦 Zero external dependencies: Uses only .NET built-in APIs
  • 🔒 Thread-safe: AsyncLocal support for async/await
  • 💎 Strongly typed: Generic constraints ensure type safety

Why Root Contracting?

Traditional Design by Contract tools face a sustainability challenge - most extensions are abandoned or dormant because they require custom compilers/preprocessors that must constantly keep up with language evolution.

Root contracting solves this with a simple utility library approach:

  • ✅ No custom tooling - Uses standard C# features only (no IL weaving, no code generation)
  • ✅ Language-independent design - Same approach can be ported to Java, Go, Python, Rust
  • ✅ Zero dependencies - Only .NET built-in APIs (System.Text.Json, System.Reflection)

Learn more:


API Reference

📖 Complete Documentation: API_REFERENCE.md — configuration, exceptions, best practices, and performance

uContract.NET's public API is organized into 4 categories:

Category Methods Purpose
Preconditions Require, RequireNotNull, RequireNotEmpty Validate inputs at method entry
Postconditions Ensure, EnsureNotNull, EnsureResult, EnsureImmutableCollection, EnsureAssignable Verify results and state changes
Invariants Invariant, InvariantNotNull Enforce class-level constraints
Helpers Check, Ignore, Old, Imply, IfAndOnlyIf, FollowsFrom, CheckUnsupportedOperation Runtime assertions and utilities

Configuration

⚙️ Detailed Configuration: USAGE_EXAMPLES.md - Configuration Examples

Control contract evaluation at runtime via environment variables.

Environment Variables

Variable Values Default Purpose
DBC on/off on Default for every type that sets no flag of its own
DBC_PRE on/off on Preconditions — overrides DBC
DBC_POST on/off on Postconditions — overrides DBC
DBC_INV on/off on Invariants — overrides DBC
DBC_CHECK on/off on Check statements — overrides DBC
DBC_DOC on/off off Diagnostic output (independent of the above)

Resolution order, highest first: the per-type flag, then DBC, then the default. The per-type flags override DBC rather than selecting a subset of it — so DBC=on DBC_INV=off leaves invariants disabled, and DBC=off DBC_PRE=on enables preconditions only. See ADR-0004, which rejects the alternative where a per-type flag could not re-enable what DBC had disabled.

Configuration is read once and frozen at process start, so setting a variable afterwards has no effect. To see what actually resolved rather than what you asked for, read ContractConfiguration.PreconditionsSource and its siblings — each names whichever of the per-type flag, DBC, or the default decided that type — or set DBC_DOC=on to have the summary written to stderr.

Turning Contracts Off

Contracts are enabled by default. These commands turn them off:

Windows (PowerShell):

$env:DBC="off"

Linux/macOS:

export DBC=off

Key Behaviors

  • Default: All contracts enabled in every build configuration; set DBC=off to disable
  • Parameter validation: Always executed (even when DBC is disabled)
  • Zero overhead: Conditions are never evaluated when disabled (lazy evaluation), except by Ignore, which is never disabled

Examples

📚 More Examples: USAGE_EXAMPLES.md (15+ real-world scenarios)


Requirements

  • .NET 8.0 or later
  • C# 12 (nullable reference types enabled)
  • No external dependencies (uses only .NET built-in APIs)

Supported Types

  • ✅ All reference types (class, record) (see Trimming and Native AOT below)
  • ✅ All value types (struct, record struct)
  • ✅ Generic types with appropriate constraints
  • ✅ Collections (IEnumerable, List, Array, ImmutableList, etc.) (see Trimming and Native AOT below)

Trimming and Native AOT

Old<T>() and EnsureAssignable<T>() have limited support in trimmed and Native AOT applications in 2.0.0. Setting DBC_POST=off turns off every postcondition check, these two helpers included.

  • Old<T>() copies the value with reflection-based JSON serialization, which is off by default in trimmed and Native AOT apps. Set the MSBuild property JsonSerializerIsReflectionEnabledByDefault to true in the application project. Under Native AOT, also preserve the copied types with DynamicDependency. In a trimmed app without Native AOT the MSBuild property was enough in the measured case; preserving the copied types with DynamicDependency is the safe choice there too (a copied type that lost members to the trimmer was not measured).
  • EnsureAssignable<T>() compares the members of T in every kind of build. A caller that forwards its own generic type parameter to EnsureAssignable<T> gets trim warning IL2091 until it adds [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] to that parameter.
  • Under Native AOT it throws InvalidOperationException when it reaches a nested object or collection element whose type's members are not preserved. To resolve it, either list the top-level member as assignable (use the plain member name: patterns are regular expressions matched against top-level member names only) or preserve the type with DynamicDependency. This includes framework types such as Uri, Exception, Version, Lazy<T>, SemaphoreSlim and CancellationTokenSource. For these, listing the member is the practical choice. Members declared as a value type or as string — for example DateTime, Guid, decimal, enums, Nullable<T>, value tuples and user structs — compare normally. A member declared as object that holds a boxed value, and an array or collection whose elements are classes, follow the nested rule above and can throw. A nested type that genuinely has no properties or fields (for example an empty marker class) also throws under Native AOT; for such a type only listing the member (or DBC_POST=off) helps.
  • Assignable patterns are unanchored regular expressions: listing Customer also exempts other top-level members whose names contain it (for example CustomerId). Anchor the pattern (^Customer$) to exempt exactly one member. An auto-property declared on the compared type is compared twice, as the property and as its backing field <Customer>k__BackingField, so an anchored pattern must cover both (for example ^(Customer|<Customer>k__BackingField)$). An inherited auto-property is compared once, as the property, so an anchored pattern for it needs only the property name (^Customer$).
  • In a trimmed app without Native AOT, a nested type whose property getter was removed by the trimmer makes EnsureAssignable<T>() throw InvalidOperationException naming the property ("… no get method is visible through the compared type"). The same two ways out apply: list the top-level member as assignable, or preserve the type with DynamicDependency.
  • Not detected: a type of which only some members are preserved (the missing members are skipped silently) and, in a trimmed app without Native AOT, a nested type whose members were removed entirely (it compares as equal). Detection also depends on the data: a null member, the same instance on both sides, or an empty collection is not inspected.

Preserve types with DynamicDependency. In this example Old<T>() copies a Customer and EnsureAssignable<T>() compares the nested Address, so under Native AOT both types need their members preserved (the application project also sets JsonSerializerIsReflectionEnabledByDefault to true):

using System.Diagnostics.CodeAnalysis;
using uContract;

public sealed record Address(string Street, string City);

public sealed class Customer
{
    public string Name { get; set; } = "";
    public Address Address { get; set; } = new("1 Main St", "Springfield");

    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Customer))]
    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Address))]
    public void Rename(string newName)
    {
        Customer oldState = Contract.Old(() => this)!;
        Name = newName;
        Contract.EnsureAssignable(this, oldState, nameof(Name));
    }
}

Differences from Java Version

uContract.NET maintains semantic parity with the Java version while leveraging modern .NET features.

Syntax Differences

Feature Java C#
Method naming require(), old(), imply() Require(), Old(), Imply()
Lambda syntax () -> condition () => condition
Functional types BooleanSupplier, Supplier<T> Func<bool>, Func<T>
Null checks @NonNull annotation where T : class constraint

.NET Improvements

Feature Java C#
Parameter validation ❌ Not validated ✅ Always validated (even when DBC off)
Exception messages ⚠️ Inconsistent format ✅ Unified format
Async support ❌ ThreadLocal (no async) ✅ AsyncLocal (async/await safe)
Configuration ⚠️ Public static fields ✅ Encapsulated ContractConfiguration class
Nullability ⚠️ Annotations only ✅ Compiler-enforced nullable reference types
Old<T>() overloads ⚠️ Two overloads (Jackson TypeReference<T> needed for generics) ✅ Single Old<T>(Func<T>) — reified generics make the type hint unnecessary
Description auto-capture ❌ Annotation mandatory on every call ✅ Condition-based methods (Require/Ensure/Invariant/Check) have [CallerArgumentExpression] overloads — compiler captures the condition's source text when description is omitted

Note on description auto-capture: C# 10's [CallerArgumentExpression] lets Contract.Require(() => balance >= 0) work without a manual description — the compiler embeds the condition's source text at compile time, so the exception message shows exactly what failed. Available on the four condition-based methods; value-based null-check methods (RequireNotNull, RequireNotEmpty, EnsureNotNull, InvariantNotNull) do not have CAE overloads due to C# overload-resolution collisions with their existing generic signatures. See ADR-0018 for the full rationale and limitations.

Note on Old<T>(): Java's old(Supplier<T>, TypeReference<T>) overload exists to work around type erasure. .NET's reified generics preserve full generic type information at runtime, so JsonSerializer.Deserialize<T>(json) already receives the closed-generic type with no external hint needed. See ADR-0016 for the full rationale.

Migrating from Java

Renames to be aware of when porting Java code that uses uContract:

Java (2.0.1) uContract.NET Notes
reject() Ignore() Renamed for semantic clarity
ClassInvariantViolationException InvariantViolationException Shorter name; same role in the exception hierarchy
old(Supplier<T>, TypeReference<T>) (removed) Use Old<T>(Func<T>) — no type hint needed (ADR-0016)

Exception messages differ. Java throws with the raw description text ("x positive"); uContract.NET prefixes the contract type ("Precondition violated: x positive"). If your tests or log parsers assert on exception messages, update them accordingly. The unprefixed description remains available via the exception's Description property.

Environment variables (DBC, DBC_PRE, DBC_POST, DBC_INV, DBC_CHECK) keep the same names and semantics as the Java version.

Example Comparison

Java:

// Java
require("x positive", () -> x > 0);
var oldBalance = old(() -> balance);
ensure("balance decreased", () -> balance < oldBalance);

C#:

// C#
Contract.Require("x positive", () => x > 0);
var oldBalance = Contract.Old(() => balance);
Contract.Ensure("balance decreased", () => balance < oldBalance);

Documentation

User Documentation

  • 📖 API_REFERENCE.md - API reference

    • Configuration, exception hierarchy, best practices, performance
    • Per-method signatures and XML docs available via IDE IntelliSense
  • 📚 USAGE_EXAMPLES.md - Real-world examples

    • 15+ practical scenarios (banking, e-commerce, inventory, user management)
    • Quick DDD examples with links to complete guide
    • Configuration examples and testing patterns
  • 🏗️ DDD_INTEGRATION_GUIDE.md - DDD integration guide

    • 10+ DDD patterns with best practices
    • Specifications, repositories

Developer Documentation

  • CLAUDE.md - Development guidelines and session context
  • docs/adr/ - Architecture Decision Records

Contributing

Contributions are welcome!

Before contributing:

  1. Read the Architecture Decision Records

License

MIT License — Copyright (c) 2025-2026 uContract.NET Contributors. See LICENSE for details.

This project is a derivative work of the Java uContract library by Teddy Chen and contributors, which is licensed under the Apache License 2.0. See THIRD-PARTY-NOTICES.txt for the required attribution and license text.


References

Original Java Version (2.0.1)

This .NET port is based on Java uContract 2.0.1 (GitLab commit: cb1e03f)

Design by Contract Theory


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 was computed.  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 was computed.  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.
  • net8.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on uContract:

Package Downloads
ezDDD.Entity

Core DDD tactical patterns for ezDDD.NET - Entity, AggregateRoot, EsAggregateRoot, and DomainEvent. Features: • IEntity<TId> - Covariant entity interface • IValueObject - Marker for immutable value objects • IDomainEvent - Base domain event with metadata • AggregateRoot<TId, TEvent> - State sourcing aggregate root • EsAggregateRoot<TId, TEvent> - Event sourcing with R1/R2/R3 correctness rules • DomainEventTypeMapper - BiMap-based event type mapping • Template method pattern for invariant checking • Event replay from history Designed for Domain-Driven Design with event sourcing and state sourcing.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 238 10/4/2026
1.0.0 496 7/3/2026 1.0.0 is deprecated because it has critical bugs.