ValueTypes 1.0.0 License Info

ValueTypes 1.0.0

ValueTypes

An easy-to-use implementation of ValueObjects from DDD. ValueTypes

Icon made by Freepik from Flaticon

Summary

There is no out-of-the box solution in C# for structural equality. The goal of this little library is to make it as easy as possible to use ValueTypes.

The focus of this library is on the developer. It should as painless as possible to implement Value Types!

Usage

Make all of your value types inherit from the abstract Value class. You'll be required to implement the GetValues method, which returns IEnumerable<ValueBase>.

ValueBase is the base type of Value, and contains an implicit cast from every base type besides object. Thus implementing GetValues() is as simple as yielding your primitives; the cast handles the rest for you!

public override IEnumerable<ValueBase> GetValues()
{
    yield return _count;     // int
    yield return _factor;    // double
    yield return _type;      // string
    // More as needed...
}

This form is useful when your Value contains simple types and a list of other simple types. But we want this to be easy, and plenty of ValueTypes do not have this, so how do we make our lives easier?

Value.Yield()

If all you need to do is list the components to be included then Value.Yield(); is the answer. Here's a complete example from the unit tests, for a 2d point:

public sealed class Point2d : Value
{
    public int X { get; }
    public int Y { get; }

    public Point2d(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }

    protected override IEnumerable<ValueBase> GetValues() => Yield(X, Y);
}

The signature is Yield(params ValueBase[] values) so you're free to pass as many arguments as needed. The implict cast takes care of everything else for you. It doesn't get much simpler than that!

Value.Group()

There are occassions where this will not be sufficient, however. For example, sometimes a collection of values should be Equal regardless of their order.

As an example, let's say that we have a Wallet type which contains a collection of dollar bills. Suppose in the implementation we have a simple List<DollarBill>, where DollarBill is abstract, and has subtypes for each dollar bill; Twenty : DollarBill for example.

What we want is for a wallet containing these five bills $1, $1, $5, $20, $20 to match another wallet containing $20, $20, $1, $5, $1. The order in which these values get yielded causes these not to be considered equal.

Value.Group() to the rescue! In lieu of using Yield() you just use the helper method Group().

Another example, from the unit tests, is shown below. Here we're modelling a line segment. Crucially, a line segment is undirected. That is, it doesn't have a begin and end, it's simply the line joining two points. Thus, the order of the two Point2ds that indicate the two ends is not important to equality.

public class LineSegment : Value
{
    public Point2d A { get; }
    public Point2d B { get; }

    public LineSegment(Point2d a, Point2d b)
    {
        A = a;
        B = b;
    }

    protected override IEnumerable<ValueBase> GetValues() => Group(A, B);
}

In this way, the line from (2,3) to (4,5) is the same as the segment from (4,5) to (2,3).

If we were to use Yield() we would be implementing a Vector, which is directed.

Comments

If you have a combination of unordered collections, and single members, you can just Concat the two lists together (from Yield() and Group()).


Dates deserve special care, which is why there's no cast from a DateTime. The problem with dates is that their equality depends on the resolution you need.

Suppose you have a TransactionDate which is only supposed to indicate a date. That is, {07/04/2019 01:23:45} should be the same as {07/04/2019 08:06:13}. (Because the year, month, and day match and we don't care about time components.)

This is likely already handled by your business logic, but I don't make that assumption.

So what do I do?

The recommended solution for this example would be to Yield date.ToString("yyyyMMdd"). This converts the DateTime into a string with precision appropriate for the desired Equality.


Tuples of values work as expected.

For example, a tuple of values behaves correctly:

var t1 = (new Point2d(4, 8), new Point2d(15, 16));
var t2 = (new Point2d(4, 8), new Point2d(15, 16));

Assert.AreEqual(t1, t2); // Works

There is no handling for classes which have tuple members. This, though, seems super unlikely, so I'm not worried about it.