Table of Contents

Class ValueObject

Namespace
FunctionalDdd
Assembly
FunctionalDdd.DomainDrivenDesign.dll

Base class for value objects in Domain-Driven Design. A value object represents a descriptive aspect of the domain with no conceptual identity. Value objects are immutable, defined by their attributes, and support structural equality.

public abstract class ValueObject : IComparable<ValueObject>, IEquatable<ValueObject>
Inheritance
ValueObject
Implements
Derived
Inherited Members
Extension Methods

Examples

Simple value object example:

public class Address : ValueObject
{
    public string Street { get; }
    public string City { get; }
    public string State { get; }
    public string PostalCode { get; }

    private Address(string street, string city, string state, string postalCode)
    {
        Street = street;
        City = city;
        State = state;
        PostalCode = postalCode;
    }

    // Factory method with validation
    public static Result<Address> TryCreate(
        string street, string city, string state, string postalCode) =>
        (street, city, state, postalCode).ToResult()
            .Ensure(x => !string.IsNullOrWhiteSpace(x.street), 
                   Error.Validation("Street is required"))
            .Ensure(x => !string.IsNullOrWhiteSpace(x.city),
                   Error.Validation("City is required"))
            .Map(x => new Address(x.street, x.city, x.state, x.postalCode));

    // Define what makes two addresses equal
    protected override IEnumerable<IComparable> GetEqualityComponents()
    {
        yield return Street;
        yield return City;
        yield return State;
        yield return PostalCode;
    }

    // Domain behavior
    public string GetFullAddress() => 
        $"{Street}, {City}, {State} {PostalCode}";
}

// Usage
var address1 = Address.TryCreate("123 Main St", "Springfield", "IL", "62701");
var address2 = Address.TryCreate("123 Main St", "Springfield", "IL", "62701");

// Structural equality
address1.Value == address2.Value; // true - same attributes

Value object with rich behavior:

public class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }

    private Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public static Result<Money> TryCreate(decimal amount, string currency = "USD") =>
        (amount, currency).ToResult()
            .Ensure(x => x.amount >= 0, Error.Validation("Amount cannot be negative"))
            .Ensure(x => x.currency.Length == 3, 
                   Error.Validation("Currency must be 3-letter ISO code"))
            .Map(x => new Money(x.amount, x.currency.ToUpperInvariant()));

    protected override IEnumerable<IComparable> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency;
    }

    // Domain operations return new instances (immutability)
    public Result<Money> Add(Money other) =>
        Currency != other.Currency
            ? Error.Validation($"Cannot add {other.Currency} to {Currency}")
            : new Money(Amount + other.Amount, Currency).ToResult();

    public Money Multiply(decimal factor) =>
        new Money(Amount * factor, Currency);
}

Derived value object example:

public class InternationalAddress : Address
{
    public string Country { get; }

    private InternationalAddress(
        string street, string city, string state, 
        string postalCode, string country) 
        : base(street, city, state, postalCode)
    {
        Country = country;
    }

    // Include base components plus additional ones
    protected override IEnumerable<IComparable> GetEqualityComponents()
    {
        foreach (var component in base.GetEqualityComponents())
            yield return component;
        yield return Country;
    }
}

Remarks

Value objects are one of the three main building blocks in DDD (along with Entities and Aggregates). Key characteristics:

  • Identity: Defined by attribute values, not by a unique identifier
  • Immutability: Once created, a value object's state cannot change
  • Equality: Two value objects with the same attributes are considered equal
  • Interchangeability: Value objects with equal attributes can be freely substituted
  • Side-effect free: Methods on value objects don't modify state, they return new instances

Value Objects vs. Entities:

  • Value Object: Defined by attributes (e.g., Address, Money, EmailAddress)
  • Entity: Defined by identity (e.g., Customer, Order, Product)

Benefits of using value objects:

  • Type safety: EmailAddress is more expressive than string
  • Validation: Encapsulate validation logic in the value object
  • Rich behavior: Add domain-specific methods (e.g., Money.Add, Temperature.ToFahrenheit)
  • Immutability: Prevents accidental state changes
  • Testability: Pure functions are easy to test

When to use value objects:

  • The concept measures, quantifies, or describes something in the domain
  • It can be modeled as immutable
  • It models a conceptual whole by grouping related attributes
  • Equality should be based on the whole set of attributes
  • There's domain behavior associated with the concept

Methods

CompareTo(ValueObject?)

Compares the current value object with another value object of the same type.

public virtual int CompareTo(ValueObject? other)

Parameters

other ValueObject

The value object to compare with this instance.

Returns

int

A value less than zero if this instance is less than other; zero if they are equal; or greater than zero if this instance is greater than other.

Remarks

Components are compared in order. The first non-equal component determines the result. This enables value objects to be sorted and used in ordered collections.

Exceptions

ArgumentNullException

Thrown when other is null.

ArgumentException

Thrown when other is not of the same type as this instance.

Equals(ValueObject?)

Determines whether the specified value object is equal to the current value object. Two value objects are equal if they have the same type and all equality components are equal.

public bool Equals(ValueObject? other)

Parameters

other ValueObject

The value object to compare with the current value object.

Returns

bool

true if the value objects have the same type and equal components; otherwise, false.

Remarks

This implements structural equality based on the components returned by GetEqualityComponents(). Value objects of different types are never equal, even if they have the same component values.

Equals(object?)

Determines whether the specified object is equal to the current value object.

public override bool Equals(object? obj)

Parameters

obj object

The object to compare with the current value object.

Returns

bool

true if the specified object is a value object of the same type with equal components; otherwise, false.

GetEqualityComponents()

When overridden in a derived class, returns the components that define equality for this value object.

protected abstract IEnumerable<IComparable> GetEqualityComponents()

Returns

IEnumerable<IComparable>

An enumerable of comparable objects that represent the value object's attributes. Two value objects are equal if their equality components are equal in the same order.

Examples

protected override IEnumerable<IComparable> GetEqualityComponents()
{
    yield return Street;
    yield return City;
    yield return PostalCode;
}

Remarks

This method is used by Equals(ValueObject?) and GetHashCode() to determine equality. Components should be returned in a consistent order.

Guidelines:

  • Return all properties that define the value object's identity
  • Use yield return for lazy evaluation
  • For derived classes, include base.GetEqualityComponents() first
  • Return components in a consistent, deterministic order
  • Include all properties that should affect equality comparison

GetHashCode()

Returns a hash code for this value object based on its equality components.

public override int GetHashCode()

Returns

int

A hash code combining all equality components.

Remarks

The hash code is cached for performance since value objects are immutable. This ensures consistent hash codes for the lifetime of the object and improves performance when used as dictionary keys or in hash-based collections.

Operators

operator ==(ValueObject?, ValueObject?)

Determines whether two value objects are equal.

public static bool operator ==(ValueObject? a, ValueObject? b)

Parameters

a ValueObject

The first value object to compare.

b ValueObject

The second value object to compare.

Returns

bool

true if both are null or have equal components; otherwise, false.

operator >(ValueObject?, ValueObject?)

Determines whether the first value object is greater than the second.

public static bool operator >(ValueObject? left, ValueObject? right)

Parameters

left ValueObject

The first value object to compare.

right ValueObject

The second value object to compare.

Returns

bool

true if left is greater than right; otherwise, false.

operator >=(ValueObject?, ValueObject?)

Determines whether the first value object is greater than or equal to the second.

public static bool operator >=(ValueObject? left, ValueObject? right)

Parameters

left ValueObject

The first value object to compare.

right ValueObject

The second value object to compare.

Returns

bool

true if left is greater than or equal to right; otherwise, false.

operator !=(ValueObject?, ValueObject?)

Determines whether two value objects are not equal.

public static bool operator !=(ValueObject? a, ValueObject? b)

Parameters

a ValueObject

The first value object to compare.

b ValueObject

The second value object to compare.

Returns

bool

true if the value objects have different components; otherwise, false.

operator <(ValueObject?, ValueObject?)

Determines whether the first value object is less than the second.

public static bool operator <(ValueObject? left, ValueObject? right)

Parameters

left ValueObject

The first value object to compare.

right ValueObject

The second value object to compare.

Returns

bool

true if left is less than right; otherwise, false.

operator <=(ValueObject?, ValueObject?)

Determines whether the first value object is less than or equal to the second.

public static bool operator <=(ValueObject? left, ValueObject? right)

Parameters

left ValueObject

The first value object to compare.

right ValueObject

The second value object to compare.

Returns

bool

true if left is less than or equal to right; otherwise, false.