Implementing ValueObject's Equality - Efficiently - Part 1

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, and NOT the focus of this post.

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.

For example, when writing on a whiteboard you want to use a blue marker. If you have many blue markers, 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.

String is definitely a value object, because you don't care about which instance of "Hello World!" you are holding.

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. This is something I'd like to avoid, if possible.

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.

public abstract class ValueObject
{
    protected static bool EqualOperator(ValueObject left, ValueObject right)
    {
        if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
        {
            return false;
        }
        return ReferenceEquals(left, null) || left.Equals(right);
    }

    protected static bool NotEqualOperator(ValueObject left, ValueObject right)
    {
        return !(EqualOperator(left, right));
    }

    protected abstract IEnumerable<object> GetAtomicValues();

    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType())
        {
            return false;
        }

        ValueObject other = (ValueObject)obj;
        IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
        IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator();
        while (thisValues.MoveNext() && otherValues.MoveNext())
        {
            if (ReferenceEquals(thisValues.Current, null) ^
                ReferenceEquals(otherValues.Current, null))
            {
                return false;
            }

            if (thisValues.Current != null &&
                !thisValues.Current.Equals(otherValues.Current))
            {
                return false;
            }
        }
        return !thisValues.MoveNext() && !otherValues.MoveNext();
    }

    public override int GetHashCode()
    {
        return GetAtomicValues()
         .Select(x => x != null ? x.GetHashCode() : 0)
         .Aggregate((x, y) => x ^ y);
    }
    // Other utility methods
}

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 object/type 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 really fast. That is the approach I took to build ValueObjectComparer<T>.

Here is an example of what a Value Object looks like. No inheritance, so this can also be used for structs.

The Equals method simply delegates to the ValueObjectComparer<SomeObject>. Same for the IEquatable<SomeObject> interface implementation. The IQuatable interface uses a strongly typed Equals method, which is better for structs, as it will avoid boxing.

The difference between the built-in object's Equals and IEquatable<T> is that the compiler will pick the most specific matching Equals(T other).

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);
}

Ignoring Certain Properties

Some Value Objects have calculated properties, and using them in the comparison is not necessary. You might want to use the ValueObjectComparer<T> for other, non-value-object, types. So you might want to ignore certain properties for short.

Simply add the [Ignore] attribute to the property, and it won't be used for equality.

[Ignore]
public int NotUsed { get; set; }

What about GetHashCode?

When you compile the previous class, Visual Studio will give you a warning about not overriding 'GetHashCode'. ValueObjectComparer<T> also comes with a GetHashCode method that takes care of this.

public override int GetHashCode()
  => ValueObjectComparer<SomeObject>.Instance.GetHashCode();

The whole class:

public class SomeObject : IEquatable<SomeObject>
{
  public static bool operator ==(SomeObject left, SomeObject right)
    => ValueObjectComparer<SomeObject>.Instance.Equals(left, right);

  public static bool operator !=(SomeObject left, SomeObject right)
    => !(left == right);

  public string Name { get; set; }

  public int Age { get; set; }

  [Ignore]
  public int NotUsed { get; set; }

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

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

  public override int GetHashCode()
    => ValueObjectComparer<SomeObject>.Instance.GetHashCode();
}

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 |
|---------------------------------------------------- |-----------:|-----------:|-----------:|
|                     UsingHCValueObjectsThatAreEqual |   8.657 ms |   8.512 ms |   9.511 ms |
|                     UsingMyValueObjectsThatAreEqual |   9.878 ms |   9.497 ms |  11.083 ms |
|                     UsingMSValueObjectsThatAreEqual | 282.006 ms | 276.861 ms | 296.591 ms |
|          UsingHCValueObjectsThatAreEqualWithNesting |  24.081 ms |  22.648 ms |  28.420 ms |
|          UsingMyValueObjectsThatAreEqualWithNesting |  33.038 ms |  32.148 ms |  35.188 ms |
|          UsingMSValueObjectsThatAreEqualWithNesting | 607.281 ms | 592.930 ms | 647.931 ms |
|                    UsingSameInstanceOfHCValueObject |   4.238 ms |   4.193 ms |   4.281 ms |
|                    UsingSameInstanceOfMyValueObject |   4.339 ms |   4.223 ms |   4.549 ms |
|                    UsingSameInstanceOfMSValueObject |   3.698 ms |   3.676 ms |   3.747 ms |
|                  UsingHCValueObjectsThatAreNotEqual |   9.275 ms |   9.127 ms |   9.318 ms |
|                  UsingMyValueObjectsThatAreNotEqual |   8.491 ms |   8.393 ms |   8.660 ms |
|                  UsingMSValueObjectsThatAreNotEqual | 233.310 ms | 232.583 ms | 234.565 ms |
|               UsingMyValueObjectStructsThatAreEqual |  27.393 ms |  27.334 ms |  27.443 ms |
| UsingMyValueObjectStructsThatAreEqualWithInModifier |  27.162 ms |  26.475 ms |  28.694 ms |

Using ValueObjectComparer<T> results in about a 20x-30x faster execution time then the Microsoft implementation, and is about as fast as the hardcoded version (with no nesting)!

Speedy

Classes Being Used in the Performance Tests

The UsingHCValueObjectsThatAreEqual method uses a hardcoded implementation for Equals:

public class HCValueObject : IEquatable<HCValueObject>
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public int Age { get; set; }
  public HCNestedValueObject Nested { get; set; }

  public override bool Equals(object obj)
  {
    if (object.ReferenceEquals(this, obj))
    {
      return true;
    }
    if (this.GetType() == obj?.GetType())
    {
      var other = obj as HCValueObject;
      return Equals(other);
    }
    return false;
  }

  public bool Equals([AllowNull] HCValueObject other) 
    =>  object.ReferenceEquals(this, other) 
    || (this.FirstName == other.FirstName
        && this.LastName == other.LastName
        && this.Age == other.Age
        && this.Nested == other.Nested);
}

The UsingMyValueObjectsThatAreEqual method measures performance of using the ValueObjectComparer<T> with a class:

public class MyValueObject : IEquatable<MyValueObject>
{

  public string FirstName { get; set; }
  public string LastName { get; set; }
  public int Age { get; set; }
  public NestedValueObject Nested { get; set; }
  public override bool Equals(object obj)
    => ValueObjectComparer<MyValueObject>.Instance.Equals(this, obj);
  public bool Equals([AllowNull] MyValueObject other)
    => ValueObjectComparer<MyValueObject>.Instance.Equals(this, other);
}

The similar UsingMSValueObjectsThatAreEqual method uses the Microsoft implementation:

public class MSValueObject : ValueObject, IEquatable<MSValueObject>
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public int Age { get; set; }
  public MSNestedValueObject Nested { get; set; }

  public bool Equals([AllowNull] MSValueObject other) 
    => base.Equals(other);

  protected override IEnumerable<object> GetAtomicValues()
  {
    yield return this.FirstName;
    yield return this.LastName;
    yield return this.Age;
    yield return this.Nested;
  }
}

In this test we use two instances of the same (equal) value object and the Nested property is null. We call Equals 2_000_000 times after a check that these are equal:

private const int fruityLoops = 2_000_000;
[Benchmark()]
public void UsingMyValueObjectsThatAreEqual()
{
  MyValueObject myValueObject1 = 
    new MyValueObject { FirstName = "Jefke", LastName = "Vandersmossen", Age = 43 };
  MyValueObject myValueObject2 = 
    new MyValueObject { FirstName = "Jefke", LastName = "Vandersmossen", Age = 43 };

  bool shouldBeTrue = myValueObject1.Equals(myValueObject2);
  if (!shouldBeTrue) throw new Exception();
  for (int i = 0; i < fruityLoops; i += 1)
  {
    myValueObject1.Equals(myValueObject2);
  }
}

Here are the results again when the nested object is null:

|                                              Method |       Mean |        Min |        Max |
|---------------------------------------------------- |-----------:|-----------:|-----------:|
|                     UsingHCValueObjectsThatAreEqual |   8.657 ms |   8.512 ms |   9.511 ms |
|                     UsingMyValueObjectsThatAreEqual |   9.878 ms |   9.497 ms |  11.083 ms |
|                     UsingMSValueObjectsThatAreEqual | 282.006 ms | 276.861 ms | 296.591 ms |

And when the nested object has been set:

|                                              Method |       Mean |        Min |        Max |
|---------------------------------------------------- |-----------:|-----------:|-----------:|
|          UsingHCValueObjectsThatAreEqualWithNesting |  24.081 ms |  22.648 ms |  28.420 ms |
|          UsingMyValueObjectsThatAreEqualWithNesting |  33.038 ms |  32.148 ms |  35.188 ms |
|          UsingMSValueObjectsThatAreEqualWithNesting | 607.281 ms | 592.930 ms | 647.931 ms |

Value objects are Immutable

Value Objects are immutable, and in this case, we can use the Flyweight pattern.

In this case the first object.ReferenceEquals will hit most of the time, with this performance as a result:

|                                              Method |       Mean |        Min |        Max |
|---------------------------------------------------- |-----------:|-----------:|-----------:|
|                    UsingSameInstanceOfHCValueObject |   4.238 ms |   4.193 ms |   4.281 ms |
|                    UsingSameInstanceOfMyValueObject |   4.339 ms |   4.223 ms |   4.549 ms |
|                    UsingSameInstanceOfMSValueObject |   3.698 ms |   3.676 ms |   3.747 ms |

The Docs implementation is a lot slower, mainly because they don't use the object.ReferenceEquals. Adding this check to Equals results in similar performance as the rest.

When possible, use the Flyweight pattern for your Value Objects. Using this pattern for frequently used values can give a significant performance benefit!

Inequality

What is the performance when two instances are not equal?

|                                              Method |       Mean |        Min |        Max |
|---------------------------------------------------- |-----------:|-----------:|-----------:|
|                  UsingHCValueObjectsThatAreNotEqual |   9.275 ms |   9.127 ms |   9.318 ms |
|                  UsingMyValueObjectsThatAreNotEqual |   8.491 ms |   8.393 ms |   8.660 ms |
|                  UsingMSValueObjectsThatAreNotEqual | 233.310 ms | 232.583 ms | 234.565 ms |

Again the hardcoded version and "Just Once" reflection is very close.

Value Objects can be .NET Value Types

The last performance test UsingMyValueObjectStructsThatAreEqual uses a struct instead of a class:

public struct MyValueObjectStruct : IEquatable<MyValueObjectStruct>
{
  public static bool operator==(in MyValueObjectStruct left, in MyValueObjectStruct right)
  => ValueObjectComparerStruct<MyValueObjectStruct>.Instance.Equals(left, right);

  public static bool operator !=(in MyValueObjectStruct left, in MyValueObjectStruct right)
    => !(left == right);

  public string FirstName { get; set; }
  public string LastName { get; set; }
  public int Age { get; set; }
  public MyNestedValueObject Nested { get; set; }

  public override bool Equals(object obj)
    => ValueObjectComparerStruct<MyValueObjectStruct>.Instance.Equals(this, obj);
  public bool Equals(MyValueObjectStruct other)
    => ValueObjectComparerStruct<MyValueObjectStruct>.Instance.Equals(this, other);
}

Please note that now I am using the ValueObjectComparerStruct<T> class. With struct, we need to be extra careful that they don't get boxed, and that is why implementing IEquatable<T> is essential.

|                                              Method |       Mean |        Min |        Max |
|---------------------------------------------------- |-----------:|-----------:|-----------:|
|                     UsingHCValueObjectsThatAreEqual |   8.657 ms |   8.512 ms |   9.511 ms |
|                     UsingMyValueObjectsThatAreEqual |   9.878 ms |   9.497 ms |  11.083 ms |
|                     UsingMSValueObjectsThatAreEqual | 282.006 ms | 276.861 ms | 296.591 ms |
|               UsingMyValueObjectStructsThatAreEqual |  27.393 ms |  27.334 ms |  27.443 ms |
| UsingMyValueObjectStructsThatAreEqualWithInModifier |  27.162 ms |  26.475 ms |  28.694 ms |

Comparing this to the class version does show some performance loss. Why? Because structs are always copied, and the current IEquality does not pass structs by reference. Unfortunately, .NET does not allow you to use Expressions (which I use to implement everything) with the in parameter modifier which is part of C# 7.2.

So how does everything work? You can read all about this in the next blog post.

Source, please?

You can find all my code in this repo.

If you just want to use this class, I have made this available as a NuGet package. Search for U2U.ValueObjectComparers. It is a .NET Standard 2.0 library.