Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do the `EqualOperator()` and `NotEqualOperator()` methods work in this `ValueObject` implementation (Microsoft Docs)?

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;
     }
 }
like image 609
Matt Griffiths Avatar asked Mar 09 '18 14:03

Matt Griffiths


1 Answers

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.

like image 162
Georg Avatar answered Nov 09 '22 15:11

Georg