【问题标题】:Compare Two Structs Using Standard Method (Byte Comparison/Reflection) Even If Equals Method Exists即使存在 Equals 方法,也使用标准方法(字节比较/反射)比较两个结构
【发布时间】:2016-07-13 06:28:14
【问题描述】:

我有一些简单的结构,它覆盖了Equals() 方法:

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);
    }
}

根据MSDN,没有Equals()方法的值类型比较如下:

如果当前实例和 obj 的字段都不是引用类型,则 Equals 方法对内存中的两个对象进行逐字节比较。否则,它使用反射来比较obj和这个实例的对应字段。

我希望使用引用的方法而不是使用Pair 自己的Equals() 方法来比较Pairs,以便通过以下测试:

[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));
}

这最终应该像结构的ReferenceEqual 一样工作。这可能吗?理想情况下,我想用原来的ValueType.Equals() 方法替换比较。

编辑:

我真正的愿望是能够向这样的类添加代码协定:

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
        }
    }
}

想象一下我这样使用 holder 对象:

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

如果set 不是_item = value; 而是_item = _item;,则合约不会抱怨,因为该示例将使用Pair&lt;T&gt;Equals() 方法,它表示p1a 和p2 相等。如果它改为使用原始的ValueType.Equals() 方法使用字节比较/反射,则将正确违反合同并且会发现错误。

如果使用对象,则合约将改为 Contract.Ensures(ReferenceEqual(_item, value),但这不适用于值类型(结构)。

关键是我不知道THolder&lt;T&gt; 中的类型,所以我无法引入自己的自定义相等比较器,即使我想要。

【问题讨论】:

  • 这是一个有趣的问题,但我能问一下目的是什么吗?你想完成什么更大的任务?
  • 我有一个集合库(C5 by Sestoft),Update(item) 方法将使用其定义的相等比较器在集合中找到等于 item 的项目,并将其替换为 @ 987654343@。之后,我不能使用相等比较器来确保集合包含item,因为即使它只包含旧项目也是如此。如果我有一个对象,使用引用相等会很好,但对于没有意义的结构。但是,使用“原始”Equals 行为会。
  • @lund.mikkel 您能否为您的收藏类型提供自定义EqualityComparer&lt;Pair&lt;T&gt;&gt;
  • 是的,但这正是我试图不使用/解决的问题。我的问题是我希望将代码合同添加到一个方法中,例如Add(item) 用于列表,以确保添加的项目实际上已添加到列表中。假设列表已经包含示例中的 p1a:如果我添加 p2,列表实现可以简单地复制 p1a,因为根据相等比较器它是相等的,并且合同将错误地确认该项目已添加,因为 coll.Count(x =&gt; x.Equals(item)) 递增一个。我不能使用coll.Count(x =&gt; ReferenceEqual(x, item),因为 x 可能是一个结构

标签: c# reflection struct compare equals


【解决方案1】:

这可以使用反射来完成。以下解决方案基于博客文章 Strong Typed, High Performance Reflection with C# Delegate 中的代码,但代码已缩短为专门为 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>));
}

使用上述方法检索ValueTypesEqual() 方法,示例中的测试将如下所示:

[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));
}

【讨论】:

  • 这似乎不适用于可移植类库。
猜你喜欢
  • 1970-01-01
  • 2011-04-26
  • 2019-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-18
  • 1970-01-01
相关资源
最近更新 更多