views:

142

answers:

3

I'm attempting to use reflection to determine the result of a call to .Equals on two different but "equal" instances of a type.

My method would be something like:

public static bool TypeComparesProperties(Type t)
{
    // return true if (an instance of t).Equals(a different instance of t)
    //  will be true if all publicly accessible properties of the instances
    //  are the same
}

By way of example:

string a = "Test";
string b = "Test";
bool areEqual = a.Equals(b);   // areEqual will be true
// so:
TypeComparesProperties(typeof(string));   // should return true

However, given:

public class MyComplexType
{
    public int Id { get; set; }
    public string MyString { get; set; }
}

MyComplexType a = new MyComplexType {Id = 1, MyString = "Test"};
MyComplexType b = new MyComplexType { Id = 1, MyString = "Test" };
bool areEqual = a.Equals(b);   // areEqual will be false
// so:
TypeComparesProperties(typeof(MyComplexType));   // should return false

If I implemented IEquatable<MyComplexType> on my class as follows, I'd get true instead:

public class MyComplexType : IEquatable<MyComplexType>
{
    public int Id { get; set; }
    public string MyString { get; set; }

    public bool Equals(MyComplexType other)
    {
        return (Id.Equals(other.Id) && MyString.Equals(other.MyString));
    }
}

I figure I can probably do it by instantiating two instances using reflection, then setting all properties to appropriately typed default values. That's a lot of work though and a lot of overhead, and I think I'd run into problems if there was no empty constructor on the type.

Any other ideas?


Edit:

It seems that people are confused by my intentions. I apologise. Hopefully this will clarify:

I have a method which should compare two objects to the best of its abilities. Simply calling .Equals() won't suffice, because either:

  1. The objects will be value types or will implement IEquatable in a nice way and I'll get a true response. Great!
  2. The objects may have all the same properties and be "equal", but because they're different instances, I'll get a false response. I can't tell whether this false is because the objects are not "equal", or because they're just different instances.

So in my mind, the comparison method should:

  1. Examine the object type to see whether it's Equals method will return true for two different instances with the same public properties.
  2. If so, call the Equals method and return the result
  3. If not, have a look at all the properties and fields and compare them as well as I can to determine whether they're equal

Now, I understand that I could just skip to step 3, but if there's a way to determine ahead of time whether it's necessary, that'd be a time-saver.


Edit 2:

I'm gonna close this for a couple of reasons:

  1. The more I talk about it, the more I realise that what I asked isn't what I really wanted to do
  2. Even if I did want to do this, there's no real shortcut at all. RE the earlier edit, I should just skip to step 3 anyway.

Thanks all for your input.

+1  A: 

Honestly, if your comparing valuetypes the default equals should be fine. However when using the default implementation of equals on reference types it operates differently, more of a reference pointer equallity. To get true object level Equals operations to work you either have to implement IEquatable, override the Equals method on each class, or use reflection to compare each public property. Take a look at this link and it may or may not help:

SO - Internal Equals Object

Joshua Cauble
Thanks Joshua, I understand that, but I need to know whether, for a particular reference type, I **can** use Equals on it to get an "equality". If I just call Equals on two unknown objects and it returns false, I'm none the wiser.
Damovisa
There are two ways you can do that check at runtime. Both checks can exist in your TypesComparesProperties method1) Check to see if the Type implements the IEquatable interface, if it does you can use it.2) See if the class overrides the Equals method from object. To do that see post http://stackoverflow.com/questions/1760388/as3-reflection-how-to-find-out-if-a-method-is-overriddenThat should allow you to do a type check before trying to run the equals command so you know if you have to do more involved reflection based equality operations vs a simple objA.Equals(objB) command.
Joshua Cauble
Just saw Marc's answer. That solution looks pretty nice. Considering I have a project where we now need to go implement equality on a bunch of objects I think I may have to give his solution a go and see how it works out for my app as well.
Joshua Cauble
+1  A: 

Try this on for size.

public static bool TypeComparesProperties(Type t)
{
    Type equatableInterface = typeof(IEquatable<>);

    if (t.GetInterface(equatableInterface.Name) != null)
    {
        return true;
    }
    else
    {
        Type objectClass = typeof(Object);
        MethodInfo equalsMethod = t.GetMethod("Equals", new Type[] { typeof(object) });

        return equalsMethod.DeclaringType != objectClass;
    }
}

First, the method checks to see if IEquatable<> has been implemented on the type. If it has, return true. If it hasn't, check to see if the type has overridden the Equals(object) method. If it has, return true; otherwise (if the Equals method is the default method declared in Object), return false.

Adam Maras
Why not just use `EqualityComparer<T>.Default.Equals(x,y)`, which supports `IEquatable<T>`, falling back to `object.Equals`, and also handles nulls, `Nullable<T>`, etc.
Marc Gravell
I suppose I probably just reinvented the wheel to some extend. Wasn't familiar with the `EqualityComparer<T>` class.
Adam Maras
+1  A: 

You could do something based on this answer, noting that this compares the public properties, not the fields, and that Equals could do anything crazy it wants. It wouldn't be hard to change it to use fields instead, but... to be honest, I'm not sure what the driver is here. I'd just write the Equals(object) and Equals(MyComplexType) and use EqualityComparer<T>.Default.Equals(x,y) (which handles IEquatable<T>, nulls, Nullable<T>, etc - using object.Equals(x,y) as the fallback strategy).

For something that I think meets your updated need:

public static bool AreEqual<T>(T x, T y) {
    if(ReferenceEquals(x,y)) return true;
    if(x==null || y == null) return false;
    IEquatable<T> eq = x as IEquatable<T>;
    if(eq == null) return PropertyCompare.Equal(x,y);
    return eq.Equals(y);
}
Marc Gravell
This is actually closer to what I really wanted to do. My bad. I've updated my question to clarify.
Damovisa