Home

Awesome

Build status

Value

is a pico library (or code snippets shed) to help you to easily implement Value Types in your C# projects without making errors nor polluting your domain logic with boiler-plate code.

Value

Value Types?

Domain Driven Design (DDD)'s Value Object being an oxymoron (indeed, an object has a changing state by nature), we now rather use the "Value Type" (instance) terminology. But the concept is the same as described within Eric Evan's Blue book.

A Value Type:

    // e.g.: the following Add function does not change any existing Amount instance, it just returns a new one
    public Amount Add(Amount otherAmount) 

Reverse the trend! (we need more Value Types, and fewer Entities)

An Entity is an object that has a changeable state (often made by combining Value Objects) for which we care about its identity. Whether to use an Entity or Value Object will strongly depend on your business context (there is no silver bullet). Here are some basic samples to better grasp the difference between Value Types and Entities:

Our OO code bases are usually full of types with states and contain very few Value Type instances. DDD advises us to reverse the trend by not having Entities created by default, and to strongly increase our usage of Value Types.

This will helps us to reduce side-effects within our OO base code. A simple reflex, for great benefits.

Side effects, you said?

Yes, because one of the problem we face when we code with Object Oriented (OO) languages like C# or java is the presence of side-effects. Indead, the ability for object instances to have their own state changed by other threads or by a specific combination of previous method calls (temporal coupling) makes our reasoning harder. Doing Test Driven Development helps a lot, but is not enough to ease the reasoning about our code.

Being inspired by functional programming (FP) languages, DDD suggests us to make our OO design more FP oriented in order to reduce those painful side-effects. They are many things we can do for it. E.g.:

You are not alone

Since there is no first-class citizen for immutability and Value Types in C#, the goal of this pico library is to help you easily implement Value Types without caring too much on the boiler-plate code.

Yeah, let's focus on our business value now!


What's inside the box?

    // 1. You inherit from ValueType<T> like this:
    public class Amount : ValueType<Amount>
    {
        private readonly decimal quantity;
        private readonly Currency currency;
		
        public decimal Quantity { get { return this.quantity; } }
        public Currency Currency { get { return this.currency; } }

	...

	// 2. You (are then forced to) implement the abstract method returning the list of all your fields
	protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality()
        {
            return new List<object>() { this.quantity, this.currency }; // The line of code I was talking about
        }

        // And that's all folks!
    }


     // when one of your ValueType aggregates a IList like this
     private readonly List<Card> cards;

     //...

     protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality()
     {
         // here, you can use the ListByValue decorator to ensure a "ByValue" equality of your Type.
         return new List<object>() { new ListByValue<Card>(this.cards) };
     }
      // when one of your ValueType aggregates a Set like this
      private readonly HashSet<Card> cards;
      
      //...
     
      protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality()
      {
          // here, you can use the SetByValue decorator to ensure the "ByValue" equality of your Type.
          return new List<object>() { new SetByValue<Card>(this.cards) };
      }

Usage samples of ValueTypes

Disclaimer: for the sake of clarity, the following code samples don't have behaviours to only focus here on the Equality concern. Of course, a ValueType in DDD must embed behaviours to swallow complexity (it's not just a DTO or a POCO without responsibilities).

Code Sample of a properly implemented ValueType:

    /// <summary>
    /// Proper implementation of a ThreeeCards ValueType since the order of the cards doesn't matter during
    /// Equality. Note: the Card type is also a ValueType.
    /// </summary>
    public class ThreeCards : ValueType<ThreeCards>
    {
        private readonly HashSet<Card> cards;

        public ThreeCards(string card1Description, string card2Description, string card3Description)
        {
            this.cards = new HashSet<Card>();

            this.cards.Add(Card.Parse(card1Description));
            this.cards.Add(Card.Parse(card2Description));
            this.cards.Add(Card.Parse(card3Description));
        }

        protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality()
        {
            // we decorate our standard HashSet with the SetByValue helper class.
            return new List<object>() { new SetByValue<Card>(this.cards) };
        }
    }

Code Sample of a bad ValueType implementation:

    /// <summary>
    /// Bad ValueType implementation of ThreeCards since the GetAllAttributesToBeUsedForEquality() method 
    /// returns the set directly, without decoring it with the SetByValue helper.
    /// </summary>
    public class ThreeCardsBadlyImplementedAsValueType : ValueType<ThreeCards>
    {
        private readonly HashSet<Card> cards;

        public ThreeCardsBadlyImplementedAsValueType(string card1Description, string card2Description, string card3Description)
        {
            this.cards = new HashSet<Card>();

            this.cards.Add(Card.Parse(card1Description));
            this.cards.Add(Card.Parse(card2Description));
            this.cards.Add(Card.Parse(card3Description));
        }

        protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality()
        {
            // BAD IMPLEMENTATION HERE: should have returned "new SetByValue<Card>(this.cards)" instead of "this.cards"
            return new List<object>() { this.cards };
        }
    }

Now, let's have a look a 2 tests that clarify the impact of those 2 implementations:

        [Test]
        public void Should_consider_equals_two_ValueType_instances_that_aggregates_equivalent_SetByValue()
        {
            var threeCards = new ThreeCards("AS", "QD", "2H");
            var sameThreeCards = new ThreeCards("2H", "QD", "AS");

            Check.That(threeCards).IsEqualTo(sameThreeCards);
        }

        [Test]
        public void Should_consider_Not_equals_two_badly_implemented_ValueType_instances_that_aggregates_equivalent_HashSet()
        {
            var threeCards = new ThreeCardsBadlyImplementedAsValueType("AS", "QD", "2H");
            var sameThreeCards = new ThreeCardsBadlyImplementedAsValueType("2H", "QD", "AS");

            Check.That(threeCards).IsNotEqualTo(sameThreeCards);
        }