When it comes to programming, correctness is the name of the game. Every developer aims to understand, model, and limit potential outliers when executing code because those unknown variables can lead to exceptions and critical failures. There are many techniques developers use, but I recently discovered a new library (at least to me) in the .NET community that aims to help developers constrain potential inputs using value objects.
In this post, we’ll write a quick sample using Vogen to demonstrate how value objects can make our code clearer and less error-prone.
What is a Value Object?
A value object represents a logical concept but is a .NET primitive value such as int
, bool
, string
, and more. A straightforward example might be a birth date. Most developers would define a birth date using the DateTime
type, hoping that the variable name clarifies the value’s intention.
The drawback to this code is nothing stops a developer from unintentionally using the birthDate
variable incorrectly.
While the code technically works, it may not be what the developers intended logically. The birth date may or may not be the release date of a movie, and it’s difficult to tell if this code is “correct.” Let’s fix it using value objects.
Looking at this code, you can see that the developer intended to set a movie release date to the same value as the birth date. The goal is to minimize the chance of autocompleting your way into logical bugs that may be difficult to track down.
Yes, this can seem overly ceremonious, so consider the benefits and drawbacks before deciding if you want to adopt it.
Now, let’s see what Vogen is about.
What is Vogen?
Vogen is a NuGet library that utilizes source generators to generate value objects, taking much of the ceremony out of model creation. The library creates factory methods, comparisons, validation, and serializers on partial struct
and class
implementations.
Let’s start by adding Vogen to a .NET console application.
In my example, I’ll create a PacMan
class that requires a FavoriteGhost
property to have a valid value of Ghost
.
Our value object will be Ghost
; everyone knows that the ghosts that haunt Pac-Man include Blinky, Pinky, Inky, and Clyde. Let’s start with the unconstrained approach using Vogen.
After the source generators have run, you’ll now have public static readonly
instances of each ghost on the Ghost
struct, allowing our code to run and set the FavoriteGhost
property on our pacMan
instance.
You can create any Ghost
you like using the From
method on the struct.
If your value objects are unconstrained, this might be a good time to stop, but we want to limit our Ghost
values. Let’s rework our Ghost
struct.
With some extra code, we can now validate that all values used to create a Ghost
fit within a defined set of values.
Let’s see the use of Vogen in a complete C# sample.
Conclusion
Using value objects is an efficient way to constrain inputs and outputs logically. It helps you reflect logical constraints in the codebase and offers readability levels that could be lost using primitive types. With the addition of Vogen, you can remove boilerplate code and get straight to using value objects, with the benefits of quickly accessing the underlying primitive value through explicit and implicit means.
I think the next step for folks is likely to take an existing part of a codebase and see if converting it to use value objects improves readability and correctness.
I hope you enjoyed this post. As always, thanks for reading and sharing my posts.