Domain-Driven Design - Value Objects in C#
Before you look at the Value Objects example in C#, you can see at my post on what Value Objects are in Domain-Driven Design.
Value Object base class in C#
public abstract record ValueObject<T> : IEquatable<T> where T : IEquatable<T>
{
protected T Value { get; }
protected ValueObject(T value)
{
this.Validate(value);
this.Value = value;
}
protected abstract void Validate(T value);
public bool Equals(T? other)
{
return this.Value.Equals(other);
}
public static bool operator ==(ValueObject<T> a, T b) => a.Equals(b);
public static bool operator !=(ValueObject<T> a, T b) => !a.Equals(b);
}
About this code snippet:
- It is a
record
: Records (C# 9) are primarily built for better supporting immutable data models. - It is
abstract
: This prevents this record from being instantiated and allows to declare abstract methods. T
extends fromIEquatable
:T
is the basic value type of the Value Object, so ifT
extends fromIEquatable
, it forces that basic value type of the Value Object to be equatable.- It extends from
IEquatable<T>
: Allows comparing this Value Object with its basic data type. - It calls an abstract method called Validate in the constructor: So we are validating the value of the Value Object before creating it.
Value Object example in C#
public sealed record FirstName : ValueObject<string>
{
public const UInt16 MaxLength = 50;
public string Name => this.Value;
private FirstName(string value) : base(value)
{
}
public static FirstName Create(string value)
{
return new FirstName(value);
}
protected override void Validate(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(nameof(value));
if (value.Length > FirstName.MaxLength)
throw new ArgumentOutOfRangeException(nameof(value));
}
}
About this code snippet:
- It is
sealed
: Prevent another class from extends from this one. - It overrides the Validate method with validation rules.
- No public
constructor
: Other classes cannot create instances of this class, except for nested classes. - To create instances of this Value Object we do not call the constructor directly, but it uses the Static Factory Method pattern instead.