简体   繁体   中英

What is the best way to test for object equality - without overriding Equals & GetHashCode, or implementing IEquatable<T>?

I'd like to check for equality among two objects that have no Public Properties. However, I don't want to override the Equals & GetHashCode method, or implement IEquatable. For example, consider the following code:

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

I know the standard method is to override Equals & GetHashCode, but for various reasons, I don't want to change any code in the class. Plus, there are no public properties, so I can't compare these. Is there any other way of implementing this?

For example, via reflection? Or perhaps by serialising the Objects to JSON, and comparing the resulting strings?

Thanks.

You could create a custom IEqualityComparer<T> implementation that uses reflection:

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

You can't change the result of the Equals instance method without overriding it. So you'll need to use IEqualityComparer<T> which needs to be passed explicitly to the code that requires equality checks. Luckily most built in collections accept such an equality comparer as a parameter to their constructor.

Implementing IEqualityComparer<T> with reflection seems to be your only option if you don't want to change the original class in any way.

You can get the FieldInfo using GetField("_personName",BindingFlags.NonPublic) and then call GetValue on it to get the value.

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

You could add a member function to MyClass and check the private fields there:

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

or add extension method if you can't change the MyClass implementation, and compare using reflection as CodeInChaos described:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM