Value Object: a better implementation

By Vladimir Khorikov

This post is about a better implementation of Value Object.

Value Object: a better implementation

I believe you know what a value object is. Here’s a thorough article about it and what differentiates it from an Entity if you need a refresher: Entity vs Value Object: the ultimate list of differences.

In short, value objects are the building blocks of your domain model. Ideally, you want any concept, however small it is, to be represented by a value object. Got a user with an email? Wrap this email in a separate value object class. Are there several fields that represent an address? Even better, extracting them into a value object would reduce the number of properties the user entity needs to work with.

For many years, I’ve been using an implementation of it which I presented in the DDD in practice course. Here it is:

What it does is it handles the repeating parts of the equality members and provides extension points for deriving classes to do the actual comparison and hash code calculation.

Here’s what an Address value object deriving from that base class could look like:

As you can see, the base value object class does a pretty good job hiding most of the boilerplate code related to comparison operations. All you need to do is declare two methods. The first one is EqualsCore whose parameter other is already strongly typed, so you don’t need to deal with conversion or anything like that. And the second one is GetHashCodeCore. It doesn’t bring a lot of additional value as you could just override the standard GetHashCode, but the benefit of having this additional abstract method is that it helps you to not forget about defining it in the deriving classes.

It’s possible to move even more responsibilities to the base ValueObject class. For example, I saw implementations where the base class used .NET reflection to get the list of all fields and properties in the deriving class and perform the comparison automatically. While it might seem like a good idea as it allows you to reduce the amount of code in Address even further, I would recommend against it.

The reason why is because such approach fails in two scenarios:

  • It doesn’t work if the value object contains a collection. Collections need special treatment: taking each element and comparing them one by one instead of simply calling Equals() on the collection instance.
  • It doesn’t work if you need to exclude one of the fields from the comparison. It might be the case that not all properties in a value object created equal. There could be some Comment field that doesn’t need to be compared when evaluating equality. At the same time, there’s no way to inform the value object about this. All fields in the derived class will be taken into account and compared against each other.

So, the actual comparison logic in value objects should be implemented consciously, there’s no way you can delegate it to an automatic tool. Hence you need to do a little bit of work in the derived classes by declaring EqualsCore and GetHashCodeCore.

However, it is still possible to reduce the amount of work needed. Instead of doing the actual comparison and hash code calculation in the derived classes, you can ask them to enumerate the members that participate in the comparison and do the rest of the work in the base class.

This idea was proposed by a reader of this blog, Steven Roberts. Here’s how the new ValueObject class looks:

And this is the updated Address:

As you can see, there’s a lot more going on in the new version of the base class. It doesn’t require you to implement EqualsCore anymore. Instead, you only need to provide the list of components that comprise the class. The comparison is done by using SequenceEqual() on the two sets of such components. GetHashCode is also taken care of: it now takes each component and uses it to build up the resulting hash code.

As for Address, all it needs to do is this:

This approach reduces the amount of repeating code in the derived classes and at the same time doesn’t exhibit the drawbacks of the implementation with the fully automatic comparison. You can still choose which fields you want to take into consideration. And if the value object contains collections, it will work too. All you need to do is enumerate all collection elements alongside with other properties.

This is what you would need to do should you decide to add a list of tenants to the Address value object:

Conclusion

I like this implementation a lot as it’s simpler than the one I used previously. All kudos to Steve Roberts.

Related articles





  • Stanko Culaja

    Great solution!

    Do you plan to move this implementation to CSharpFunctionalExtensions repo and nuget?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      I would like to, but not sure how to do that yet. That would be a breaking change for everyone who upgrades from the previous version.

      • Stanko Culaja

        Yes, it is a problem.

        Maybe to put it in a different namespace and mark previous implementation as obsolete. Those are the breaking changes but it is not hard to align the broken classes with new implementation (especially if you have unit tests).

        • http://enterprisecraftsmanship.com/ Vladimir Khorikov

          Either that or force folks to modify their Value Objects by introducing the breaking change with a thorough description of how to do that. Will need to test the most convenient option.

  • Pavel Shilyagov

    Or you can just use F# 🙂
    It’s a shame that we still need to implement structural equality manually in 2017.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Records in C# could help alleviate this problem.

  • Amir Shitrit

    Should Tenants list be sorted first in order for the SequenceEquals to work well?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      That would depend on the requirements. If the position of the tenants is not important, then yes, we would need to sort them first. If it is, then we’d need to keep the collection as is to make sure two addresses are equal only if their tenants positioned the same way.

  • Stefan Bertels

    You should have a look at LanguageExt.Core by Paul Louth (on github/nuget). He solved thus problem in a type-safe way (Record). It’s an awesome library!

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Create library, but I don’t like relying on .NET reflection for this particular use case.

      • Stefan Bertels

        Have a deeper look.
        Does not use reflection AFAIK.
        Generates code, of course. AFAIK this is the only type safe solution removing boiler plate (until record types will arrive in C# 9.0?).

  • ardalis

    I assume you’re familiar with Jimmy Bogard’s ValueObject type here: https://lostechies.com/jimmybogard/2007/06/25/generic-value-object-equality/

    This is what Julie Lerman and I used in the Domain-Driven Design Fundamentals course (http://bit.ly/PS-ddd). It has the advantage of also implementing IEquatable, and it doesn’t require any special code in the child classes. It does, however, rely on reflection, so there are performance tradeoffs. For most business software applications, though, this is unlikely to pose a (legitimate) concern.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      I’m familiar with Jimmy Bogard’s implementation. It has the drawbacks I described in the article when I referred to implementations relying on .NET reflection: you can’t omit particular members from the comparison and you can’t deal with collections. I agree that it’s enough for most simple cases, though.

      It has the advantage of also implementing IEquatable

      There’s no need in implementing IEquatable, this interface was introduced specifically to avoid boxing/unboxing of .NET value types (structs) when dealing with comparison. As long as you implement ValueObject as class, you can safely omit implementing IEquatable.

      • Joseph N. Musser II

        It saves a cast though, which could add up if the BCL is using EqualityComparer<SomeValueObject>.Default against a lot of items.

        • http://enterprisecraftsmanship.com/ Vladimir Khorikov

          True, but I don’t think an additional cast on a reference type is something that would be noticeable in a typical enterprise application. Adherence to YAGNI is more important in this context.

          • Joseph N. Musser II

            I don’t see it as a YANGI thing because it’s an ubiquitous thing that has no cost. If you’re going to simplify in the name of YAGNI to the point where we’re getting rid of ubiquitous BCL vocabulary, then it would make more sense to make GetHashCode virtual and return 0. Unlike IEquatable, implementing GetHashCode does actually exact a cost on every programmer implementing a derived class. Perf in hashtables will go down unless they do override, but behavior will be correct.

          • Joseph N. Musser II

            On top of that, it’s also probably a good idea to add a obj.GetType() ==
            this.GetType()
            check in a public sealed
            Equals(T) method which then delegates to a protected abstract EqualsCore(T) method. It keeps the symmetric property of equality when a more derived class is provided.

            Once you add that method, all you need is to add : IEquatable<T>.

          • http://enterprisecraftsmanship.com/ Vladimir Khorikov

            Additional code always entails cost, there’s rarely such thing as cost-free code. You might argue that this cost is not big and I would agree. But the benefit of having such code is also small, so all things being equal, I prefer adherence to YAGNI.

            Regarding GetHashCode, I haven’t tested it, but I think the performance impact of such an overriding would be more than that of an additional cast for reference types. I might be wrong here, though.

            Regarding obj.GetType() == this.GetType(), this check is simulated by this code:

            var valueObject = obj as ValueObject;
            if (ReferenceEquals(valueObject, null))
            return false;

            No need to do the additional type check.

          • Joseph N. Musser II

            Actually, here’s a demo showing that the additional type check is necessary:

            var address = new Address(“”, “”, “”);
            var derived = new DerivedAddress(“A”, “”, “”, “”);

            address.Equals(derived) // True
            derived.Equals(address) // False

            public class DerivedAddress : Address
            {
            public string Country { get; }

            public DerivedAddress(string country, string street, string city, string zipCode)
            : base(street, city, zipCode)
            {
            Country = country;
            }

            protected override bool EqualsCore(Address other)
            {
            return other is DerivedAddress derived
            && base.EqualsCore(derived)
            && Country == derived.Country;
            }
            }

            Address.Equals(object) should be able to tell that a derived class was handed in and return false, but it’s not.

          • http://enterprisecraftsmanship.com/ Vladimir Khorikov

            Good point. I’ll add this check.

  • Husain Abdali

    Let me first thank you for your great blog and Pluralsight courses. Well, this implementation is very much similar to a beautiful implementation I have seen in CodePlex.

    The base class
    https://dddsamplenet.codeplex.com/SourceControl/latest#DDDSample-Vanilla/Domain/Cargo/Delivery.cs

    One of its derived classes
    https://dddsamplenet.codeplex.com/SourceControl/latest#DDDSample-Vanilla/Domain/Cargo/Delivery.cs

    I would really love to hear any input you might have about it.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      It’s indeed very similar. The only thing I would improve the version from CodePlex is I would move `operator ==` and `operator !=` to the base class. No need to redefine them in the deriving classes.

  • Harry McIntyre

    Using a standard collection type as a Value Object property is perhaps an example of Primitive Obsession in itself 🙂 We need an EquitableImmutableList