9

In Domain Driven Design, we're introduced to the concept of a ValueObject, where objects don't carry an identity.

Microsoft have provided an implementation of their ValueObject in their Microservices series, where they override Equals() so that two ValueObject's with the same values are considered identical.

I've included their implementation below, but my question is relating to the EqualOperator() and NotEqualOperator() methods - how does this work? when are they called?

I'm familiar with operator overloads, but this seems to be an implementation I've not seen before, and I can't find any documentation around it.

Here is the implementation:

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

    // Other utilility methods
}

Here's an example of their object in use:

public class Address : ValueObject
{
    public String Street { get; private set; }
    public String City { get; private set; }
    public String State { get; private set; }
    public String Country { get; private set; }
    public String ZipCode { get; private set; }

    private Address() { }

    public Address(string street, string city, string state, string country,
        string zipcode)
    {
        Street = street;
        City = city;
        State = state;
        Country = country;
        ZipCode = zipcode;
    }

    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;
     }
 }
Matt Griffiths
  • 1,142
  • 8
  • 26

1 Answers1

3

Actually, I find it astonishing that Microsoft implemented a value type using a class. Usually, structs are much better for this purpose, unless your value objects get very large. For most usages of value types such as coordinates or colors, this is not the case.

Leaving this discussion aside, what happens here is the following: If you implement a value object, you need to implement Equals and GetHashCode correctly, which includes consistently to each other. However, these two methods are actually not very difficult but verbose to implement: You need to cast the object and then check each of its properties. In case you are using classes, you have an additional boilerplate factor which is the fact that you typically want to speed up equality check using the reference equality check. That is, two objects need not be the same to be equal, but if they are the same, then they are also equal.

The class you depicted here is an attempt to support this consistency problem and boilerplate problem for value objects that are using classes by abstracting away quite a few commonalities. All you need to provide are the fields that make up the identity. In most cases, these are simply all fields. You iterate them using a co-method.

Now, for the actual question of when EqualOperator and NotEqualOperator are actually called, I would guess they are simply helper functions to make the implementation of operators more easy: You would provide an overloaded == operator that simply returns EqualOperator and != that simply returns NotEqualOperator. You might ask why the value type base class does not have these operators? Well, I guess this is because this would mean that the compiler would allow you to apply == and != to different types of value objects using the overloaded operator.

Georg
  • 5,626
  • 1
  • 23
  • 44
  • 4
    Structs have drawbacks - no way to hide default constructor, no inheritance, poor support in Entity Framework < Core 2.0, etc. – guillaume31 Mar 09 '18 at 15:59
  • I would agree with you regarding the structs, but I think @guillaume31 hit the nail on the head with this example in it's support for EF. Thanks for the response, that's exactly what I needed to know. – Matt Griffiths Mar 09 '18 at 18:40
  • 1
    In the current version of the docs, the == operator calls the `Equals` and not the `EqualOperator` method. – jasdefer Apr 03 '22 at 05:26