U2U.ValueObjectComparers 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package U2U.ValueObjectComparers --version 0.1.0
NuGet\Install-Package U2U.ValueObjectComparers -Version 0.1.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="U2U.ValueObjectComparers" Version="0.1.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add U2U.ValueObjectComparers --version 0.1.0
#r "nuget: U2U.ValueObjectComparers, 0.1.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install U2U.ValueObjectComparers as a Cake Addin
#addin nuget:?package=U2U.ValueObjectComparers&version=0.1.0

// Install U2U.ValueObjectComparers as a Cake Tool
#tool nuget:?package=U2U.ValueObjectComparers&version=0.1.0

Implementing ValueObject's Equality - Efficiently

Equality

This blog discusses how to implement a Value Object's Equals method efficiently.

What are Value Objects?

In Domain Driven Design objects are divided into two groups: Entities and Value Objects.

Entities are objects that have an identity and life cycle.

Value Objects are objects that don't have any real identity and are mainly used to describe aspects of an entity, such as your name which is of type string. String is a value object, because with value objects you don't care which instance you are holding. For example, when writing one a whiteboard you want to use a blue marker. If you have many markers which are blue, do you care which one you are holding? If so, then that marker is an entity, if not it is a value object. Entities are equal when they have the same identity, value objects are equal when all properties that define one are equal.

Implementing Equality for Value Objects

To implement equality for a value object we need to compare each of its properties for equality (You could say that a value object's identity is defined by all of its properties). This is not hard, but it is repetitive work. Each time you add a new property you have to update the Equals method to use that property too.

The Microsoft Approach

There are implementations for a ValueObject base class, which takes care of most of the work, for example, the one from Microsoft Docs.

Here you need to override the GetAtomicValues method.

In case of Address (copied from Docs):

protected override IEnumerable<object> GetAtomicValues()
{
  // Using a yield return statement to return each element one at a time
  yield return Street;
  yield return City;
  yield return State;
  yield return Country;
  yield return ZipCode;
}

Pretty simple, but I have two problems with this.

First of all, you need to inherit from the ValueObject base class. This excludes the use of Value Types as a Value Object. Value types (struct) are ideal Value Objects because they get embedded in the entities, just like built-in value objects int and others.

The second objection is that you should not forget to add an extra property to this method each time you add a property to the type...

Using Reflection

So what is the solution? Of course, you could use reflection to implement Equals like here. In this case, reflection automatically discovers all the properties and compares all of them, returning true is all properties are equal.

The problem with reflection is that it is slow, so you should limit reflection to "just once".

/slow.png

"Just Once" Reflection

There is a third approach where you use reflection to figure out what to do and generate the code so the second time things go fast. That is the approach I took to build ValueObjectComparer<T>.

Here is an example of what a Value Object looks like. The Equals method simply delegates to the ValueObjectComparer<SomeObject>. Same for the IEquatable<SomeObject> interface implementation.

public class SomeObject : IEquatable<SomeObject>
{
  public string Name { get; set; }
  public int Age { get; set; }

  public override bool Equals(object obj)
    => ValueObjectComparer<SomeObject>.Instance.Equals(this, obj);

  public bool Equals([AllowNull] SomeObject other) 
    => ValueObjectComparer<SomeObject>.Instance.Equals(this, other);
}

Performance

Let's see how the performance compares between the 'Equals' as prescribed by Microsoft or the "Just Once" reflection implementation. For this I have used the excellent BenchmarkDotNet library, and here are the results:

|                                     Method |      Mean |       Min |       Max |
|------------------------------------------- |----------:|----------:|----------:|
|            UsingMyValueObjectsThatAreEqual |  32.44 ms |  31.30 ms |  34.63 ms |
| UsingMyValueObjectsThatAreEqualWithNesting |  93.92 ms |  91.64 ms |  96.76 ms |
|         UsingMyValueObjectsThatAreNotEqual |  29.57 ms |  28.89 ms |  30.45 ms |
|            UsingMSValueObjectsThatAreEqual | 296.74 ms | 293.22 ms | 303.94 ms |
| UsingMSValueObjectsThatAreEqualWithNesting | 625.80 ms | 597.04 ms | 669.63 ms |
|         UsingMSValueObjectsThatAreNotEqual | 248.53 ms | 238.87 ms | 261.64 ms |
|      UsingMyValueObjectStructsThatAreEqual |  91.75 ms |  87.91 ms | 103.14 ms |

Using ValueObjectComparer<T> results in about a 10x faster implementation.

Speedy

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on U2U.ValueObjectComparers:

Package Downloads
U2U.BoardGames

Assembly containing a simple data model for use in training

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.2.2 2,307 2/14/2023
2.2.1 65,291 11/2/2021
2.2.0 19,199 4/7/2020
0.2.0 735 12/31/2019
0.1.1 511 12/30/2019
0.1.0 476 12/26/2019

Use at your own risk ;)