Skip to main content

Struct vs. Class

Decision guide

Use a partial struct when

  • The type wraps a single primitive (id, amount, code, flag)
  • Instances are short-lived or frequently created
  • The value is used as a dictionary key in a hot path
  • You want to avoid heap allocation for the value object itself
[ValueObject]
public readonly partial struct OrderId
{
public Guid Value { get; }
public OrderId(Guid value) => Value = value;
}

[ValueObject]
public readonly partial struct Quantity
{
public int Value { get; }
public Quantity(int value) => Value = value;
}

Structs are stack-allocated (when not boxed) and incur no GC overhead.

Use a partial class when

  • The type has multiple properties or complex behaviour
  • The type needs to inherit from a base class
  • The type participates in polymorphism
  • Reference equality is needed somewhere in the codebase
[ValueObject]
public partial class Money
{
public decimal Amount { get; }
public string Currency { get; }
}

// Works even when inheriting from application infrastructure
[ValueObject]
public partial class DomainEvent : BaseEvent
{
public Guid EventId { get; }
public DateTime OccurredAt { get; }
}

ForceClass = true

Occasionally a type must be declared as a struct for legacy reasons (e.g., JSON serialiser layout, P/Invoke interop) but needs class-style equality (reference-nullable Equals(T?)). Set ForceClass = true.

// Struct declaration retained for interop reasons
[ValueObject(ForceClass = true)]
public partial struct LegacyEventId
{
public long Value { get; }
}

// Generator emits:
sealed partial class LegacyEventId : System.IEquatable<LegacyEventId>
{
public bool Equals(LegacyEventId? other) =>
other is not null && Value == other.Value;
// ...
}