【问题标题】:What is the best way to test for object equality - without overriding Equals & GetHashCode, or implementing IEquatable<T>?测试对象相等性的最佳方法是什么 - 不覆盖 Equals 和 GetHashCode 或实现 IEquatable<T>?
【发布时间】:2011-12-06 00:20:29
【问题描述】:

我想检查两个没有公共属性的对象是否相等。但是,我不想重写 Equals & GetHashCode 方法,或者实现 IEquatable。例如,考虑以下代码:

class Program
{
    static void Main(string[] args)
    {
        Guid id = Guid.NewGuid();
        string personName = "MyName";
        MyClass object1 = new MyClass(id, personName);
        MyClass object2 = new MyClass(id, personName);
        //This returns false, but I'd like it to return true:
        Console.WriteLine(object1.Equals(object2));
        //[edit]...by using, for example:
        Console.WriteLine(ObjectsAreEqual(object1, object2));
    }
}

class MyClass
{
    private Guid _id;
    private string _personName;
    public MyClass(Guid id, string personName)
    {
        _id = id;
        _personName = personName;
    }
}

我知道标准方法是重写 Equals & GetHashCode,但由于种种原因,我不想更改类中的任何代码。另外,没有公共属性,所以我无法比较这些。有没有其他方法可以实现这一点?

例如,通过反射?或者也许通过将对象序列化为 JSON,并比较结果字符串?

谢谢。

【问题讨论】:

  • 我不确定我是否理解:如果你想将它们与 object.Equals 进行比较,你必须重写它,如果没有,你可以只为 MyClass 使用 MyEqual 方法,然后做任何你想做的事情想去那里...
  • »我想渡过那条河。不,不是通过那里那座明显的桥。不,我也不会游泳,更不用说飞了。事实上,我根本无法动弹。你能帮帮我吗?«
  • JSON 和 XML 序列化都不会帮助您,因为它们不知道私有字段
  • 乔伊,我不确定这是一个暗示,还是我错过了一些完全明显的东西的声明......!
  • Amittai,我想我的代码不是 100% 准确 - 我不一定需要使用 Object.Equals()。我可以调用一个方法: ObjectsAreEqual(obj1, obj2) 代替,该方法在不使用 Object.Equals 的情况下检测相等性。

标签: c# equality


【解决方案1】:

如果不覆盖 Equals 实例方法,您将无法更改它的结果。所以你需要使用IEqualityComparer&lt;T&gt;,它需要明确地传递给需要相等检查的代码。幸运的是,大多数内置集合都接受这样一个相等比较器作为其构造函数的参数。

如果您不想以任何方式更改原始类,使用反射实现 IEqualityComparer&lt;T&gt; 似乎是您唯一的选择。

您可以使用GetField("_personName",BindingFlags.NonPublic) 获取FieldInfo,然后对其调用GetValue 以获取值。

void Main()
{
        Guid id = Guid.NewGuid();
        string personName = "MyName";
        MyClass object1 = new MyClass(id, personName);
        MyClass object2 = new MyClass(id, personName);
        //This returns false, but I'd like it to return true:
        Console.WriteLine(object1.Equals(object2));
        //[edit]...by using, for example:
        Console.WriteLine(MyClassEqualityComparer.Instance.Equals(object1, object2));
}

public class MyClass
{
    private Guid _id;
    private string _personName;
    public MyClass(Guid id, string personName)
    {
        _id = id;
        _personName = personName;
    }
}

public class MyClassEqualityComparer:IEqualityComparer<MyClass>
{
  private static FieldInfo personNameField=typeof(MyClass).GetField("_personName",BindingFlags.Instance|BindingFlags.NonPublic);
  private static FieldInfo idField=typeof(MyClass).GetField("_id",BindingFlags.Instance|BindingFlags.NonPublic);

  public bool Equals(MyClass o1,MyClass o2)
  {
    if(o1==o2)
      return true;
    if(o1==null||o2==null)
      return false;
    string name1=(string)personNameField.GetValue(o1);
    string name2=(string)personNameField.GetValue(o2);
    if(name1!=name2)
      return false;
    Guid id1=(Guid)idField.GetValue(o1);
    Guid id2=(Guid)idField.GetValue(o2);
    return id1==id2;
  }

  public int GetHashCode(MyClass o)
  {
    if(o==null)
      return 0;
    string name=(string)personNameField.GetValue(o);
    Guid id=(Guid)idField.GetValue(o);
    return name.GetHashCode()^id.GetHashCode();
  }

  private MyClassEqualityComparer()
  {
  }

  public static readonly IEqualityComparer<MyClass> Instance=new MyClassEqualityComparer();
}

【讨论】:

  • 它不会帮助 object.Equals 问题:来自 MSDN:“...与接受 IEqualityComparer 通用接口的集合类型一起使用。在 .NET Framework 中, Dictionary 泛型集合类型接受此接口。” (msdn.microsoft.com/en-us/library/ms132151.aspx)
  • 显然不能改变.Equals的实例方法。用正常的手段根本不可能做到这一点。 (分析 API 允许这样的东西,或者您可以对生成的程序集进行后处理)。
【解决方案2】:

您可以创建一个使用反射的自定义 IEqualityComparer&lt;T&gt; 实现:

var comparer = new MyClassEqualityComparer();
Console.WriteLine(comparer.Equals(object1, object2));

// ...

public class MyClassEqualityComparer : EqualityComparer<MyClass>
{
    private static readonly string[] _names = { "_id", "_personName" };

    private static readonly FieldInfo[] _infos =
        typeof(MyClass).GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
                       .Where(fi => _names.Contains(fi.Name))
                       .ToArray();

    public override bool Equals(MyClass x, MyClass y)
    {
        return _infos.All(fi => object.Equals(fi.GetValue(x), fi.GetValue(y)));
    }

    public override int GetHashCode(MyClass obj)
    {
        unchecked
        {
            int hash = 31;
            foreach (FieldInfo fi in _infos)
            {
                object val = fi.GetValue(obj);
                hash = (hash * 17) + ((val == null) ? 0 : val.GetHashCode());
            }
            return hash;
        }
    }
}

【讨论】:

  • 但是如果没有类的公共属性,你将如何实现它呢?
【解决方案3】:

您可以向 MyClass 添加一个成员函数并检查那里的私有字段:

class MyClass
{
    private Guid _id;
    private string _personName;
    public MyClass(Guid id, string personName)
    {
        _id = id;
        _personName = personName;
    }

    public bool IsEqual(MyClass otherInstance)
    {
        return (_id == otherInstance._id && _personName == otherInstance._personName);
    }
}

如果您无法更改 MyClass 实现,则添加扩展方法,并使用反射进行比较,如 CodeInChaos 所述:

static class MyClassExtensions
{
    public static bool IsEqual(this MyClass myInstance, MyClass otherInstance)
    {
       // Reflection goes here - read all private field from myInstance     
       // and otherInstance and compare them
    }
}

【讨论】:

    猜你喜欢
    • 2023-04-09
    • 2013-08-09
    • 1970-01-01
    • 2010-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多