How to use value objects in C#

By Joydip Kanjilal

In the C# programming language, an object can be either a value type or a reference type. While an object is an object is an object, the difference between value types and reference types comes down to the nature of their variables. A variable of a value type contains an object, or an instance of the type. A variable of a reference type contains a reference to an object, or a reference to an instance of the type.

C# also lets us create what’s called value objects, a special type of object used in domain-driven design that allows us to articulate domain concepts simply, clearly, and concisely. In this article we will examine value objects, discuss their benefits, and illustrate how we can use them in C#.

[ Also on InfoWorld: The best new features in C# 12 ]

Create a console application project in Visual Studio

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2022 is installed in your system, follow the steps outlined below to create a new .NET core console application project.

We’ll use this .NET 8 console application project to work with value objects in the subsequent sections of this article.

What are value objects in C#?

In the C# language, a value object is defined as an object that can be identified only by the state of its properties. Value objects represent a descriptive aspect of a domain without having an identity. In other words, value objects represent elements of the design that we care about only in terms of their contents, not their identities.

In domain-driven design (DDD), the value object pattern represents objects without identity and supports equality based on their attributes. It should be noted here that contrary to value objects, entity objects have distinct identities. Unlike entity objects, which are defined by their identity and have mutable state, value objects are defined by their attributes and are immutable.

Immutability means you cannot change a value object once it has been created; any operations performed on value objects will create a new instance instead. The immutability of value objects has important performance benefits. Because two value objects are considered equal if they contain identical values, even if they might be different objects, they become interchangeable. In other words, immutability enables object reuse.

A value objects example in C#

Consider the following class named Address, which contains a few properties and an argument constructor.

public class Address{ public string HouseNo { get; set; } public string Street { get; set; } public string ZipCode { get; set; } public string City { get; set; } public Address(string houseno, string street, string zipCode, string city) { HouseNo = houseno; Street = street; ZipCode = zipCode; City = city; }}

You can create an instance of this class and assign values to its properties using the following code.

Address address = new Address("505", "Woodland Street", "CR2 8EN", "London");

However, because the Address class here exposes public setters, you could easily change the values of its properties from outside the class. This violates one of the basic features of value objects, namely its support for immutability.

We can fix this by redesigning the Address class as shown below.

public class Address{ public string HouseNo { get; private set; } public string Street { get; private set; } public string ZipCode { get; private set; } public string City { get; private set; } public Address(string houseno, string street, string zipCode, string city) { HouseNo = houseno; Street = street; ZipCode = zipCode; City = city; }}

As you can see above, the properties of our new Address class contain private setters, which do not allow changes to the value of any of these properties from outside the class, thereby preserving immutability. If you execute this program, you will be greeted with the error shown in Figure 1 below.

You can also implement value objects using records in C#. To do this, you use the record keyword to define a record type that encapsulates data much the same way we did with the Author class earlier. The following code snippet shows how you can define a record type in C#.

public record Address(string Houseno, string Street, string ZipCode, string City);

Best practices for using value objects in C#

When working with value objects in C#, you should follow these best practices:

Value objects encapsulate primitive types and have two main properties, i.e., they do not have an identity and they are immutable. Value objects can simplify complex data structures by encapsulating related data into a single unit. This ensures data integrity, enforces strong typing, reduces errors, and improves the readability of the source code. You can take advantage of value objects to enhance the clarity and maintainability of your C# code by providing a more expressive representation of the domain concepts.

Typically, creating value objects in C# involves overriding equality comparison and implementing appropriate value-based semantics. A typical practice involves overriding the Equals() and GetHashCode() methods, and providing other operators for comparison. Typically, we use a base class that comprises these methods that all value object implementations should extend.

In the above example, I’ve provided a basic implementation of value objects in C#. In a future post here, I’ll explore how we can implement a value object base class and discuss more advanced concepts.

© Info World