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
otherValueObjectThe 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 thanother.
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
otheris null.- ArgumentException
Thrown when
otheris 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
otherValueObjectThe value object to compare with the current value object.
Returns
- bool
trueif 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
objobjectThe object to compare with the current value object.
Returns
- bool
trueif 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
aValueObjectThe first value object to compare.
bValueObjectThe second value object to compare.
Returns
- bool
trueif 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
leftValueObjectThe first value object to compare.
rightValueObjectThe second value object to compare.
Returns
- bool
trueifleftis greater thanright; 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
leftValueObjectThe first value object to compare.
rightValueObjectThe second value object to compare.
Returns
- bool
trueifleftis greater than or equal toright; otherwise,false.
operator !=(ValueObject?, ValueObject?)
Determines whether two value objects are not equal.
public static bool operator !=(ValueObject? a, ValueObject? b)
Parameters
aValueObjectThe first value object to compare.
bValueObjectThe second value object to compare.
Returns
- bool
trueif 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
leftValueObjectThe first value object to compare.
rightValueObjectThe second value object to compare.
Returns
- bool
trueifleftis less thanright; 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
leftValueObjectThe first value object to compare.
rightValueObjectThe second value object to compare.
Returns
- bool
trueifleftis less than or equal toright; otherwise,false.