views:

275

answers:

3

Given two objected that do not contain reference loops within them, do you know a method that tests their equality in a "generic" way (through reflection)?

I basically want the same semantics as struct equivalence, only on classes.

+2  A: 

I think there is no such method available in the framework, but it's fairly easily written. Perhaps not the shortest implementation but is seems to do the job:

private bool AreEqual(object x, object y)
{
    // if both are null, they are equal
    if (x == null && y == null)
    {
        return true;
    }
    // if one of them are null, they are not equal
    else if (x == null || y == null)
    {
        return false;
    }

    // if they are of different types, they can't be compared
    if (x.GetType() != y.GetType())
    {
        throw new InvalidOperationException("x and y must be of the same type");
    }

    Type type = x.GetType();
    PropertyInfo[] properties = type.GetProperties();

    for (int i = 0; i < properties.Length; i++)
    {
        // compare only properties that requires no parameters
        if (properties[i].GetGetMethod().GetParameters().Length == 0)
        {
            object xValue = properties[i].GetValue(x, null);
            object yValue = properties[i].GetValue(y, null);

            if (properties[i].PropertyType.IsValueType && !xValue.Equals(yValue))
            {
                return false;
            }
            else if (!properties[i].PropertyType.IsValueType)
            {
                if (!AreEqual(xValue, yValue))
                {
                    return false;
                }
            } // if
        } // if
    } // for

    return true;

}
Fredrik Mörk
This has several problems, which I've come across when using this. For one thing, if there is a cycle in the property graph -- that is, if by recursively navigating an object's properties you may end up with the original object -- then this will lead to a StackOverflowException (I should have expected as much given where the code comes from). Secondly, if any of the property getter throws an exception you're pretty much screwed. These don't sound too critical, until you realize the System.Type type is both cyclic and has properties that throw. :(
Avish
While the cyclic problem can easily be solved by recursively passing around a set of "visited" (x,y) pairs and stopping the recursion when hitting a cycle, the exceptions problem can't be consistently solved (do we expect the same exception from x and y? or do we just ignore this property?). In any case, this issue is more complex than it seems and I recommend against doing this sort of comparison, unless you contain it to very specific classes (and if you do, you can just implement Equals() on them and be done with it).
Avish
@Avish: very good observations, and to some extent the reason why I normally prefer to either implement Equals or use custom comparison methods.
Fredrik Mörk
A: 

If you want to do this without doing reflection on each call, you might want to consider building a DynamicMethod on first invocation and using that instead. (I had a link to the article that does this, but I lost it - sorry - try Googling if interested.)

Dmitri Nesteruk
Phew! DynamicMethod is Reflection Emit, in other words you're creating custom MSIL for each property. Not necessarily hard, but not easy to read or debug. If performance is that much of an issue, perhaps writing an Equals or IEqualityComparer<T> that's specialised for the job would do the task rather better.
Jeremy McGee
A: 

BTW

 Expression.Lambda<Func<T,T,bool>> Compile()

can be used as a dynamic method builder.

still have to use reflection while building the Expresison

George Polevoy