views:

282

answers:

1

When comparing entities and aggregate roots I use an ABC, which I borrowed from Oren Eini: Generic Entity Equality. For value objects I was equally ingenious. I used Jimmy Bogard’s Value Object ABC: Generic Value Object Equality

Now my question is; should I be favouring inheriting these ABCs or should I perhaps be using the generic equality / comparer and composing the aforementioned behaviour instead? I intend to reuse the above equality implementations.

I think this comes back to System.Object having a default Equals implementation, which makes the language bloody easy to use but offers interesting quandaries too. Jon Skeet goes into detail here: Comparison methods

Can anyone think of any pro or cons?

  • Inheriting the ABCs is easier and offers operator overloads out of the box.
  • Should I be “dragging” two abstract classes around with me so much? It makes my inheritance graph more complex / adds coupling.
  • I could use DI to compose equality providers?

So to summarise, does anyone have any experience with using custom equality implementations for entities? My aim is to garner a fuller understanding of the implementation choices and to implement a solution, which will prolong the life time of the project (reduce entropy) and improve maintainability.

A: 

This doesn't really answer your question (sorry!), but I think it is a project which attempts to help with some of the questions you have here. I wrote this stuff a little while back but haven't done anything which is it since then.

The project is called Essence ( http://essence.codeplex.com/ ), and it uses the System.Linq.Expression libraries to generate (based on attributes) standard representations of Equals/GetHashCode/CompareTo/ToString, as well as being able to create IEqualityComparer and IComparer classes based on an argument list. (I also have some further ideas, but would like to get some community feedback before continuing too much further.)

(What this means is that it's almost as fast as being handwritten - the main one where it isn't is the CompareTo(); cause the Linq.Expressions doesn't have the concept of a variable in the 3.5 release - so you have to call CompareTo() on the underlying object twice when you don't get a match. Using the DLR extensions to Linq.Expressions solves this. I suppose I could have used the emit il, but I wasn't that inspired at the time.)

It's quite a simple idea, but I haven't seen it done before.

Now the thing is, I kind of lost interest in polishing it (which would have included writing an article for codeproject, documenting some of the code, or the like), but I might be persuaded to do so if you feel it would be something of interest.

(The codeplex site doesn't have a downloadable package; just go to the source and grab that - oh, it's written in f# (although all the test code is in c#) as that was the thing I was interested in learning.)

Anyway, here is are some sample c# examples from my test cases.

    // --------------------------------------------------------------------
    // USING MY ESSENCE LIBRARY:
    // --------------------------------------------------------------------
    [EssenceClass(UseIn = EssenceFunctions.All)]
    public class TestEssence : IEquatable<TestEssence>, IComparable<TestEssence>
    {
        [Essence(Order=0, Format="i={0},")]           public int MyInt           { get; set; }
        [Essence(Order=1, Format="s={0},")]           public string MyString     { get; set; }
        [Essence(Order=2, Format="d={0:yyyy-MM-dd}")] public DateTime MyDateTime { get; set; }

        public override int GetHashCode()                                { return Essence<TestEssence>.GetHashCodeStatic(this); }
        public override string ToString()                                { return Essence<TestEssence>.ToStringStatic(this); }
        public int CompareTo(TestEssence other)                          { return Essence<TestEssence>.CompareToStatic(this, other); }

        public static bool operator ==(TestEssence lhs, TestEssence rhs) { return Essence<TestEssence>.EqualsStatic(lhs, rhs); }
        public override bool Equals(object obj)                          { return this == (TestEssence)obj; }
        public bool Equals(TestEssence other)                            { return this == other; }
        public static bool operator !=(TestEssence lhs, TestEssence rhs) { return !(lhs == rhs); }
    }

    // --------------------------------------------------------------------
    // EQUIVALENT HAND WRITTEN CODE:
    // --------------------------------------------------------------------
    public class TestManual
    {
        public int MyInt;
        public string MyString;
        public DateTime MyDateTime;

        public override int GetHashCode()
        {
            var x = MyInt.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= (MyString == null) ? 0 : MyString.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= MyDateTime.GetHashCode();
            return x;
        }

        public static bool operator ==(TestManual lhs, TestManual rhs)
        {
            if (ReferenceEquals(lhs, null))
            {
                if (ReferenceEquals(rhs, null))
                    return true;
                return false;
            }
            if (ReferenceEquals(rhs, null))
                return false;
            if (ReferenceEquals(lhs, rhs))
                return true;
            if (typeof(TestManual) != rhs.GetType())
                return false;

            return lhs.MyInt == rhs.MyInt
                && lhs.MyString == rhs.MyString
                && lhs.MyDateTime == rhs.MyDateTime;
        }

        public override bool Equals(object obj)                 { return this == obj as TestManual; }
        public bool Equals(TestManual other)                    { return this == other; }
        public static bool operator !=(TestManual lhs, TestManual rhs) { return !(lhs == rhs); }

        public override string ToString()
        {
            if (MyString == null)
                return string.Format("i={0},d={1:yyyy-MM-dd}", MyInt, MyDateTime);

            return string.Format("i={0},s={1},d={2:yyyy-MM-dd}", MyInt, MyString, MyDateTime);
        }

        public int CompareTo(TestManual other)
        {
            if (other == null)
                return 1;
            if (ReferenceEquals(this, other))
                return 0;

            int result = 0;

            result = MyInt.CompareTo(other.MyInt);
            if (result != 0) return result;
            result = MyString.CompareTo(other.MyString);
            if (result != 0) return result;
            result = MyDateTime.CompareTo(other.MyDateTime);
            if (result != 0) return result;

            return result;
        }
    }

    // --------------------------------------------------------------------
    // --------------------------------------------------------------------
    // ALTERNATIVE USAGE
    // --------------------------------------------------------------------
    // --------------------------------------------------------------------

    class Simple
    {
        public Simple(int value) { Value1 = value; }
        public Simple(int value1, int value2) { Value1 = value1; Value2 = value2; }
        public readonly int Value1;
        public readonly int Value2;
    }

    [Test]
    public void TestReverseForwardString()
    {
        var _11 = new Simple(1, 1);
        var _12 = new Simple(1, 2);
        var _21 = new Simple(2, 1);
        var _22 = new Simple(2, 2);

        var items = new[] { _11, _12, _21, _22 };
        var reverseComparer = Essence<Simple>.CreateComparer("-Value1", "Value2");

        Array.Sort(items, reverseComparer);

        Assert.AreSame(_21, items[0]);
        Assert.AreSame(_22, items[1]);
        Assert.AreSame(_11, items[2]);
        Assert.AreSame(_12, items[3]);
    }

    [Test]
    public void TestReverseForwardLambda()
    {
        var _11 = new Simple(1, 1);
        var _12 = new Simple(1, 2);
        var _21 = new Simple(2, 1);
        var _22 = new Simple(2, 2);

        var items = new[] { _11, _12, _21, _22 };
        var reverseComparer = Essence<Simple>.CreateComparer(x => x.Action.ReverseNext, x => x.Member.Value1, x => x.Member.Value2);

        Array.Sort(items, reverseComparer);

        Assert.AreSame(_21, items[0]);
        Assert.AreSame(_22, items[1]);
        Assert.AreSame(_11, items[2]);
        Assert.AreSame(_12, items[3]);
    }

Have fun!

Paul.

Paul Westcott
Doesn't answer it no. But an interesting read nonetheless. Thanks.
Ed Blackburn