4
\$\begingroup\$

I am writing this question in the context of a DDD application. I found this code online on a number of blogs etc e.g. here and here.

public abstract class ValueObject<T> 
         where T : ValueObject<T> 
     { 
        protected abstract IEnumerable<object> GetEqualityComponents(); 


         public override bool Equals(object obj) 
       { 
            var valueObject = obj as T; 


             if (ReferenceEquals(valueObject, null)) 
                return false; 


            return EqualsCore(valueObject); 
       } 


         private bool EqualsCore(ValueObject<T> other) 
         { 
             return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); 
         }  

         public override int GetHashCode() 
         { 
             return GetEqualityComponents() 
                 .Aggregate(1, (current, obj) => current * 23 + (obj?.GetHashCode() ?? 0)); 
         } 


         public static bool operator ==(ValueObject<T> a, ValueObject<T> b) 
        { 
             if (ReferenceEquals(a, null) && ReferenceEquals(b, null)) 
                 return true; 


             if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) 
                 return false; 


             return a.Equals(b); 
         } 


         public static bool operator !=(ValueObject<T> a, ValueObject<T> b) 
         { 
             return !(a == b); 
         } 
     } 
 } 

Every Value Object in my application inherits from it. It means the methods like: Equals and GetHashCode are dealt with implicitly. The main problem I see arises if I want to add a collection e.g. array as a member of a Value Object - in this scenario the generic value object .Equals will not behave as expected as it cannot deal with collections.

Therefore:

  1. Can this class be tweaked to deal with collections?
  2. Are there any other downfalls with this approach?
  3. Is it advisable not to use a generic value class when developing a DDD application?
\$\endgroup\$
9
  • \$\begingroup\$ refactor EqualsCore to be protected virtual to allow derived classes with generic collection arguments to be able to override and determine equality themselves. \$\endgroup\$
    – Nkosi
    Dec 29, 2017 at 19:09
  • \$\begingroup\$ @Nkosi, thanks. I believe that is what the Blogger here has done: enterprisecraftsmanship.com/2016/08/04/…. If you post an answer, then I will give some credit. \$\endgroup\$
    – w0051977
    Dec 29, 2017 at 19:38
  • \$\begingroup\$ I have read many of those articles before. You should base your base value object on the one provided here by the same author. I understand what you are trying to achieve with GetEqualityComponents but that appears to be premature optimization to me. \$\endgroup\$
    – Nkosi
    Dec 29, 2017 at 20:12
  • \$\begingroup\$ @Nkosi, do you use the class in your link in a live application? I am trying to completely isolate my domain model by introducing a data model to map to the ORM. \$\endgroup\$
    – w0051977
    Dec 29, 2017 at 20:39
  • \$\begingroup\$ I have had some time to review the git hub link you posted and realize it is a more recent version of the one I suggested (it is the same author by the way). I have also been able to figure out that you can deal with collections via the GetEqualityComponents implementation by using the collections enumeration for the components. \$\endgroup\$
    – Nkosi
    Dec 29, 2017 at 21:36

1 Answer 1

1
\$\begingroup\$

Using the code in your example along with the following article for reference

Representing a collection as a Value Object

Lets start with refactoring the City value object to use the improved ValueObject

public sealed class City : ValueObject<City> {

    public string Name { get; }
    public bool IsEnabled { get; }

    public City(string name, bool isEnabled) {
        Name = name;
        IsEnabled = isEnabled;
    } 

    protected override IEnumerable<object> GetEqualityComponents() {
        yield return Name; 
        yield return IsEnabled;
    }
}

Note how GetEqualityComponents was implemented. Reflection could also have been used to enumerate the properties as needed

return this.GetType().GetProperties().Select(propInfo => propInfo.GetValue(this, null);

There is room for improvement above and I will leave that as an exercise.

For collections, the GetEqualityComponents allows for a simplified approach of returning the items used to check equality.

public class CityList : ValueObject<CityList>, IEnumerable<City> {

    private List<City> _cities { get; }

    public CityList(IEnumerable<City> cities) {
        _cities = cities.ToList();
    }

    protected override IEnumerable<object> GetEqualityComponents() {
        return this.OrderBy(c => c.Name);
    }

    public IEnumerator<City> GetEnumerator() {
        return _cities.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

Note that since the collection already implements IEnumerable one simple needs to return the desired enumeration that will be used to calculate equality.

GetEqualityComponents() proves to be a very convenient approach and really does improve and simplify how the ValueObject can be implemented. I initially though it to be a premature optimization attempt but having taken some time to review its potential I have changed my initial view of its function.

UPDATE from comments about the potential of using reflection.:

The use of reflection automates and simplifies a repetitive task with a minor performance trade off. Caching the property/field info could help a little but I personally have had no issues with it performance wise. Also using reflection grabs all while using the yield like in the example you get to be selective of which ones to use for equality. You can make an extension method to get the best of both worlds and have options.

public static class ValueObjectExtensions {
    /// <summary>
    /// Uses reflection to extract all public properties from the target value object
    /// </summary>
    public static IEnumerable<object> GetComponents<T>(this ValueObject<T> target)
        where T : ValueObject<T> {
        return target.GetType().GetProperties().Select(propInfo => propInfo.GetValue(target, null));
    }
}

and used like

protected override IEnumerable<object> GetEqualityComponents() {
   return this.GetComponents(); //<-- calling extension method
}
\$\endgroup\$
20
  • \$\begingroup\$ Thanks. I will test this in the morning at get back to you. Would you use this value object in your systems? - I am trying to establish whether the authors have published a thought exercise or practical tips. I am working towards a completely isolated domain model (with a data model that maps to orm) if that has any bearing. \$\endgroup\$
    – w0051977
    Dec 29, 2017 at 22:30
  • \$\begingroup\$ @w0051977 I have used this before in slightly different forms to suit my specific needs. I used the same articles to get a better understanding of the concept. The quoted author also states that they have used this on many projects but he tends to focus more on apply functional programming and adapting it in c#. \$\endgroup\$
    – Nkosi
    Dec 29, 2017 at 22:34
  • \$\begingroup\$ Do you know what is meant by equalscore (v equals I mean). What is meant by "core"? \$\endgroup\$
    – w0051977
    Dec 30, 2017 at 10:12
  • \$\begingroup\$ @w0051977 what do you mean? Can you clarify your last comment. \$\endgroup\$
    – Nkosi
    Dec 30, 2017 at 11:10
  • \$\begingroup\$ please look at the method name in my original post. Is there a reason it is called: equalscore? (what does the 'core' mean). \$\endgroup\$
    – w0051977
    Dec 30, 2017 at 11:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.