How to implement a value-object in C#
By: Albert Starreveld
Tags: Oop, Value Objects, Csharp

Everybody uses integers, doubles, strings, and DateTimes. So, they’re a part of the dotnet framework. But every project has project-specific data-types too.

Imagine writing software for a powerplant. The calculations are in KwH, MWh, and GWh. And they are often represented as integers or doubles in the code. It would make more sense to have a KWh data-type instead. So why not create a custom data-type yourself?

How to create a value object

Technically speaking, creating a new data-type is just a matter of creating a new struct or class. To get it to behave like any other data-type, implement the == operator and the != operator. To get it to work with LINQ operations like OrderBy(x => ..), Contains(x), Min() and Max(), implement IEquatable<T> and IComparable<T>.

In this article, we’re using a NuGet package that provides a base-class that provides all of this for us:

install-package DomainDrivenDesign.DomainObjects

Implementing all of the above now is just a matter of writing the following code:

Creating a custom data-type for KwH

This code declares a new class called KwH. Under the hood, it’s a double. It can be compared using the ==, !=, <, >, <=and the => operator.

How to use a value object

Using custom data-types instead of using the built-in data-types has implications. Assume we’ll make a KwH data-type and an MWh data-type as described earlier, and consider this code:

The business rules in your application

Replacing all the ‘int’s in this code with MWh and GWh, does not compile:

The code does not compile

Changing a data-type from integer to another data-type seems a small change, but it is a big difference. The compiler says integers can’t be compared with MWh’s.

Making a data-type explicit means you need to be explicit about what you may do with it. It’s a lot harder to write faulty code this way. It simply won’t compile.

The compiler error is correct. Compare MWh to MWh. Comparing MWh to integers should not work. It’s not explicit whether the value means an amount of MWh, KWh, or eggs for that matter. To solve the error we’ll need to replace the integer with an MWh.

Creating new instances of a custom data-type

Use a factory pattern to create new instances of a custom data-type. Adding a create method to the new data-type does just that:

Use a factory method to create a new instance of an MWh.

And use it this way:

Creating new instances of a data-type.

The single responsibility principle

Having done all of the above still does not make the code compile and reveals a design problem. It violates the single responsibility principle:

GWh can’t be multiplied with integers

The CalculateCapacity method it’s purpose is to limit something to a 100 MWh at most. While doing so, converting GWh into Kwh somehow ended up in this method too. That’s a maintenance problem because conversions potentially end up in multiple places in the code. Changing the same conversion might require multiple files to change.

To fix it, move conversions into the data-type itself:

Make it possible to convert values from one type into another

The following code is the result:

Implementing operators

And the code still does not compile. The compiler says KwH’s cannot be subtracted from each other:

Fix this error by implementing the + and the - operator:

Implementing the operators:

On a more abstract level, types may or may not be subtracted, multiplied, divided, and so forth. Performing an operation does not necessarily mean the result is of the same type.

TLDR

Using the default data-types in the dotnet framework is convenient but it does not protect the developer from writing buggy software. It makes it easy to implement things in the wrong places, too. With a code-base that’s hard to maintain as a result. Use your own custom data-types to let the code guide you to better design and to make it harder to write bugs. Consider the following method:

The business rules in your application

It’s relatively easy to create your own data-types by implementing structs or by using third party solutions like the DomainDrivenDesign.DomainObjects NuGet package. Refactor the method and change it into this: