1

I have some simple struct, which overrides the Equals() method:

public struct Pair<T> {
    public Pair(T x, T y) {
        X = x; Y = y;
    }

    public T X { get; }
    public T Y { get; }

    public override bool Equals(object obj) {
        var otherPair = (Pair<T>) obj;
        return X.Equals(otherPair.X);
    }
}

According to MSDN, value types without an Equals() method are compared as follows:

If none of the fields of the current instance and obj are reference types, the Equals method performs a byte-by-byte comparison of the two objects in memory. Otherwise, it uses reflection to compare the corresponding fields of obj and this instance.

I wish to compare Pairs using the quoted approach instead of using Pair's own Equals() method, so that the following test passes:

[Test]
public void PairsEqual()
{
    var p1a = new Pair<int>(10, 1);
    var p1b = new Pair<int>(10, 1);
    var p2 = new Pair<int>(10, 2);

    Assert.That(p1a, Is.Not.EqualTo(p2));
    Assert.That(p1a, Is.EqualTo(p1a));
    Assert.That(p1a, Is.EqualTo(p1b));
}

This should ultimately work like a ReferenceEqual for structs. Is this possible? Ideally, I would like to replace the comparison with the original ValueType.Equals() method.

Edit:

My real wish is to be able to add a code contract to a class like this:

public class Holder<T> {
    private T _item;

    public Holder(T item) {
        _item = item;
    }

    public T Item {
        get { return _item; }
        set {
            Contract.Ensures(_item.Equals(value));
            _item = value; // <-- imagine this like contained a bug
        }
    }
}

Imagine I use the holder object like this:

var holder = new Holder<Pair<int>>(p1a);
holder.Item = p2;

If set wasn't _item = value; but rather _item = _item;, the contract wouldn't complain, since the example would use Pair<T>'s Equals() method, which says that p1a and p2 are equal. If it instead use the original ValueType.Equals() method using byte comparison/reflection, the contract would have been violated correctly and the mistake would have been caugt.

Using objects, the contract would instead have been something like Contract.Ensures(ReferenceEqual(_item, value) but that doesn't work for value types (structs).

The point is that I don't know the type of T in Holder<T>, so I cannot introduce my own custom equality comparer, even if I wanted.

Mikkel R. Lund
  • 2,336
  • 1
  • 31
  • 44
  • It's an interesting problem, but can I ask what the purpose is? What bigger task are you trying to achieve? – Jon Skeet Mar 25 '16 at 13:38
  • I have a collection library (C5 by Sestoft), where the `Update(item)` method will find an item in the collection that is equal to `item` using its defined equality comparer, and replace it with `item`. Afterwards, I cannot use the equality comparer to ensure that the collection contains `item`, since that would be true even it only contained the old item. If I have an object, using the reference equality would work fine, but for structs that doesn't make sense. Using the "original" `Equals` behavior would however. – Mikkel R. Lund Mar 25 '16 at 13:46
  • @lund.mikkel Can you supply a custom `EqualityComparer>` to your collection type? – Yuval Itzchakov Mar 25 '16 at 13:54
  • Yes, but that is exactly what I'm trying not to use/work around. My problem is that I wish to add a code contract to a method, say `Add(item)` for a list, that ensures that the added item was actually added to the list. Imagine the list already contains p1a from the example: if I add p2, the list implementation could simply duplicate p1a, since it is equal according to the equality comparer, and the contract would wrongly confirm that the item was added, since `coll.Count(x => x.Equals(item))` incremented by one. I cannot use `coll.Count(x => ReferenceEqual(x, item)`, since x could be a struct – Mikkel R. Lund Mar 25 '16 at 14:06

1 Answers1

0

This can be done using reflection. The following solution is based on the code from the blog post Strong Typed, High Performance Reflection with C# Delegate, but the code has been shorten to work specifically for ValueType.Equals():

public static Func<ValueType, ValueType, bool> GetValueTypeEquals()
{
    var type = typeof(ValueType);
    var dynamicMethod = new DynamicMethod("ValueTypeEquals", typeof(bool), new[] { type, typeof(object) }, type);
    var il = dynamicMethod.GetILGenerator();
    il.Emit(OpCodes.Ldarg, 0);
    il.Emit(OpCodes.Ldarg, 1);
    il.EmitCall(OpCodes.Call, type.GetMethod(nameof(Equals), Public | Instance, null, new[] { type }, null), null);
    il.Emit(OpCodes.Ret);

    return (Func<ValueType, ValueType, bool>) dynamicMethod.CreateDelegate(typeof(Func<ValueType, ValueType, bool>));
}

Using the method above to retrieve ValueTypes's Equal() method, the test from the example will look like this:

[Test]
public void PairsEqual()
{
    var p1a = new Pair<int>(10, 1);
    var p1b = new Pair<int>(10, 1);
    var p2 = new Pair<int>(10, 2);

    var equals = GetValueTypeEquals();

    Assert.That(!equals(p1a,  p2));
    Assert.That(equals(p1a, p1a));
    Assert.That(equals(p1a, p1b));
}
Mikkel R. Lund
  • 2,336
  • 1
  • 31
  • 44