0

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.

Damovisa
  • 19,213
  • 14
  • 66
  • 88
  • This problem is equivalent to The Halting Problem; it cannot be solved in general. Can you explain why you want to do this? – Eric Lippert Dec 04 '09 at 00:45
  • RE the Halting Problem, do you mean if I was to recursively call this method on each property looking for equality? Otherwise I can't see how it's equivalent... – Damovisa Dec 04 '09 at 01:20
  • In answer to your question, it's basically an attempt to determine whether a given type is "simple" enough to compare using .Equals. I'm receiving two objects from relatively disparate systems (shared type assembly) and I want to compare them as best I can. If I can't use Equals, I'd want to examine the properties and do my best with comparison. – Damovisa Dec 04 '09 at 01:23
  • What is it you're actually trying to accomplish here? That would help a lot towards determining how to figure this out. – kyoryu Dec 04 '09 at 05:31
  • I'm not sure what the point is that you're trying to make about empty constructors etc... can you clarify what you mean here? – Marc Gravell Dec 04 '09 at 06:01
  • I've updated my question with more details. @Marc - if the objects don't have empty constructors, I can't instantiate them using reflection without passing in parameters. As I don't know what parameters are required, I can't instantiate them. – Damovisa Dec 04 '09 at 06:41
  • It'd probably be a lot harder, and take more time, to determine if Equals would do the trick than to just do the reflection in the first place. See Eric's comment about the Halting Problem. Additionally, to do this in the general case, you have to make a statement about what "equality" means for all possible types. In general, this should be up to the class being compared to know, as it's the expert in its domain, not you. In general, I'd also convert the objects (if they are values) to your own types, which will also reduce your coupling. – kyoryu Dec 04 '09 at 07:16
  • Why do you need to instantiate *anything*? Surely you're being passed the objects to compare as arguments? For (1), is the fact that it implements `IEquatable` sufficient? Or do you mean to actually inspect the IL to check what it does? (if so, skip it and just use 3 to start with) – Marc Gravell Dec 04 '09 at 07:17
  • Yeah, I'm getting the impression that skipping to step 3 is probably the only solution. I'd need to instantiate so I can control the properties set. I'm being passed 2 objects of the same type, but they could have any properties at all. *Honestly, the more I think about it, the more I think that this isn't really what I want to do anyway"... – Damovisa Dec 04 '09 at 07:25
  • Thanks for your input - it was a poor question in hindsight. I'll tag Marc's answer as correct because it was the closest to what I actually wanted. – Damovisa Dec 04 '09 at 07:31
  • Re: Halting Problem. Your question is "I have a method E(X,Y). Does E always return true when X and Y have a certain relationship?" I modify E to produce a new method F which goes into an infinite loop everywhere that E returns false. Your question is therefore equivalent to "does F(X,Y) always halt when X and Y have a certain relationship?" F is an arbitrary function, and therefore answering the question is equivalent to solving the halting problem for arbitrary functions. – Eric Lippert Dec 04 '09 at 16:24
  • @Eric - I'm with you, thanks for that explanation. It took a while to absorb, but I got there! – Damovisa Dec 06 '09 at 23:17

3 Answers3

1

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

Community
  • 1
  • 1
Joshua Cauble
  • 1,349
  • 8
  • 7
  • 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 Dec 04 '09 at 03:43
  • There are two ways you can do that check at runtime. Both checks can exist in your TypesComparesProperties method 1) 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-overridden That 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 Dec 04 '09 at 11:26
  • 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 Dec 04 '09 at 11:35
1

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
  • 26,269
  • 6
  • 65
  • 91
  • Why not just use `EqualityComparer.Default.Equals(x,y)`, which supports `IEquatable`, falling back to `object.Equals`, and also handles nulls, `Nullable`, etc. – Marc Gravell Dec 04 '09 at 05:58
  • I suppose I probably just reinvented the wheel to some extend. Wasn't familiar with the `EqualityComparer` class. – Adam Maras Dec 04 '09 at 06:29
1

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);
}
Community
  • 1
  • 1
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • This is actually closer to what I really wanted to do. My bad. I've updated my question to clarify. – Damovisa Dec 04 '09 at 07:29