While creating my testing framework I've found a strange problem.
I want to create a static class that would allow me to compare objects of the same type by their properties, but with possibility to ignore some of them.
I want to have a simple fluent API for this, so a call like TestEqualityComparer.Equals(first.Ignore(x=>x.Id).Ignore(y=>y.Name), second); will return true if the given objects are equal on every property except Id and Name (they will not be checked for equality).
Here goes my code. Of course it's a trivial example (with some obvious overloads of methods missing), but I wanted to extract the simplest code possible. The real case scenario's a bit more complex, so I don't really want to change the approach.
The method FindProperty is almost a copy-paste from AutoMapper library.
Object wrapper for fluent API:
public class TestEqualityHelper<T>
{
    public List<PropertyInfo> IgnoredProps = new List<PropertyInfo>();
    public T Value;
}
Fluent stuff:
public static class FluentExtension
{
    //Extension method to speak fluently. It finds the property mentioned
    // in 'ignore' parameter and adds it to the list.
    public static TestEqualityHelper<T> Ignore<T>(this T value,
         Expression<Func<T, object>> ignore)
    {
        var eh = new TestEqualityHelper<T> { Value = value };
        //Mind the magic here!
        var member = FindProperty(ignore);
        eh.IgnoredProps.Add((PropertyInfo)member);
        return eh;
    }
    //Extract the MemberInfo from the given lambda
    private static MemberInfo FindProperty(LambdaExpression lambdaExpression)
    {
        Expression expressionToCheck = lambdaExpression;
        var done = false;
        while (!done)
        {
            switch (expressionToCheck.NodeType)
            {
                case ExpressionType.Convert:
                    expressionToCheck 
                        = ((UnaryExpression)expressionToCheck).Operand;
                    break;
                case ExpressionType.Lambda:
                    expressionToCheck
                        = ((LambdaExpression)expressionToCheck).Body;
                    break;
                case ExpressionType.MemberAccess:
                    var memberExpression 
                        = (MemberExpression)expressionToCheck;
                    if (memberExpression.Expression.NodeType 
                          != ExpressionType.Parameter &&
                        memberExpression.Expression.NodeType 
                          != ExpressionType.Convert)
                    {
                        throw new Exception("Something went wrong");
                    }
                    return memberExpression.Member;
                default:
                    done = true;
                    break;
            }
        }
        throw new Exception("Something went wrong");
    }
}
The actual comparer:
public static class TestEqualityComparer
{
    public static bool MyEquals<T>(TestEqualityHelper<T> a, T b)
    {
        return DoMyEquals(a.Value, b, a.IgnoredProps);
    }
    private static bool DoMyEquals<T>(T a, T b,
        IEnumerable<PropertyInfo> ignoredProperties)
    {
        var t = typeof(T);
        IEnumerable<PropertyInfo> props;
        if (ignoredProperties != null && ignoredProperties.Any())
        {
            //THE PROBLEM IS HERE!
            props =
                t.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                    .Except(ignoredProperties);
        }
        else
        {
            props = 
                t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        }
        return props.All(f => f.GetValue(a, null).Equals(f.GetValue(b, null)));
    }
}
That's basically it.
And here are two test snippets, the first one works, the second one fails:
//These are the simple objects we'll compare
public class Base
{
    public decimal Id { get; set; }
    public string Name { get; set; }
}
public class Derived : Base
{    }
[TestMethod]
public void ListUsers()
{
   //TRUE
   var f = new Base { Id = 5, Name = "asdas" };
   var s = new Base { Id = 6, Name = "asdas" };
   Assert.IsTrue(TestEqualityComparer.MyEquals(f.Ignore(x => x.Id), s));
   //FALSE
   var f2 = new Derived { Id = 5, Name = "asdas" };
   var s2 = new Derived { Id = 6, Name = "asdas" };
   Assert.IsTrue(TestEqualityComparer.MyEquals(f2.Ignore(x => x.Id), s2));
}
The problem is with the Except method in DoMyEquals.
Properties returned by FindProperty are not equal to those returned by Type.GetProperties. The difference I spot is in PropertyInfo.ReflectedType.
regardless to the type of my objects,
FindPropertytells me that the reflected type isBase.properties returned by
Type.GetPropertieshave theirReflectedTypeset toBaseorDerived, depending on the type of actual objects.
I don't know how to solve it. I could check the type of the parameter in lambda, but in the next step I want to allow constructs like Ignore(x=>x.Some.Deep.Property), so it probably will not do.
Any suggestion on how to compare PropertyInfo's or how to retrieve them from lambdas properly would be appreciated.