Primitive obsession and Value Records

Leveraging records to assist overcoming primitive obsession

Home DailyDrop

Daily Knowledge Drop

The C# record type can provide a clean, quick and easy way to overcome primitive obsession.

Primitive obsession, as the name implies, is the overuse of primitive types (int, string, Guid, etc) to represent more complex business or domain concepts.


Primitive

Let's look at an example, of a (very simplified) Order entity:

public class Order
{
    public int OrderId { get; set; }

    public decimal OrderTotal { get; set; }

    public int ItemCount { get; set; }
}

On the surface, this is all good. This will work and is probably what most developer are familiar with when it comes to entities.

However, the above class structure allows for the following code:

var order = new Order
{
    OrderId = 100,
    ItemCount = 2,
    OrderTotal = 148.95m
};

// This doesn't make sense!
order.OrderId = order.OrderId * 2;
// No!?
order.ItemCount = -10;

As primitive types are used for domain business concepts such as OrderId or ItemCount, these values can be manipulated in a which doesn't make sense in their business or domain context.

Of course an option is to constantly perform validation on the Order to ensure it is in a valid state - but another option is to use Value Objects, which will be explored in the next section.


Value object

A Value object is a simple, light-weight wrapper around the primitive type, which can also provide some validation for the internal primitive type.

Value record

In the referenced article, Stephen Cleary introduces a concept called Value Record - leveraging the C# record and struct types, a simple Value Object can easily be defined with one line of code.

To replace the int OrderId above:

public readonly record struct OrderId(int Value);

Using this technique has a number of benefits:

  • immutability
  • equality, hash code and ToString support built in
  • value-type wrapper, with no additional memory allocated

Usage

The Order entity can now be updated to use the OrderId value record (as well as other properties as well):

public class Order
{
    public OrderId OrderId { get; set; }

    public OrderTotal OrderTotal { get; set; }

    public ItemCount ItemCount { get; set; }
}


public readonly record struct OrderId(int Value);

public readonly record struct OrderTotal(decimal Value);

public readonly record struct ItemCount(int Value);

And the usage:

var order = new Order
{
    OrderId = new OrderId(100),
    ItemCount = new ItemCount(2),
    OrderTotal = new OrderTotal(148.95m)
};

// An added benefit of records (over a class) 
// is that they can be printed as well
// The ToString() method can also be overwritten
// if required
Console.WriteLine(order.OrderId);

// ERROR - compilation error 
// this is now not possible as the 
// OrderId is immutable

// order.OrderId.Value = order.OrderId.Value * 2;

The output of the above:

OrderId { Value = 100 }

The value records can be expanded with additional (light-weight) validation, but the record struct method shown above allows for a quick, simple implementation.


Notes

A very useful as well as quick easy technique to implement - but also an technique which can be misused (as it is so easy to implement). Not all properties should be or need to be converted to a value object. However where it does make sense, the value record method is a slick, minimal way to achieve this.


References

Modern C# Techniques, Part 2: Value Records

Daily Drop 194: 03-11-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net record primitive