title: Compare PropertyInfo from Type.GetProperties() and lambda expressions
body: <p>While creating my testing framework I've found a strange problem.</p>
  
  <p>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.</p>
  
  <p>I want to have a simple fluent API for this, so a call like <code>TestEqualityComparer.Equals(first.Ignore(x=&gt;x.Id).Ignore(y=&gt;y.Name), second);</code> will return true if the given objects are equal on every property except <code>Id</code> and <code>Name</code> (they will not be checked for equality).</p>
  
  <p>Here goes my code. Of course it's a trivial example (with some <strong>obvious</strong> 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.</p>
  
  <p>The method <code>FindProperty</code> is almost a copy-paste from <a href="http://automapper.org/">AutoMapper library</a>.</p>
  
  <p>Object wrapper for fluent API:</p>
  
  <pre><code>public class TestEqualityHelper&lt;T&gt;
  {
      public List&lt;PropertyInfo&gt; IgnoredProps = new List&lt;PropertyInfo&gt;();
      public T Value;
  }
  </code></pre>
  
  <p>Fluent stuff:</p>
  
  <pre><code>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&lt;T&gt; Ignore&lt;T&gt;(this T value,
           Expression&lt;Func&lt;T, object&gt;&gt; ignore)
      {
          var eh = new TestEqualityHelper&lt;T&gt; { 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 &amp;&amp;
                          memberExpression.Expression.NodeType 
                            != ExpressionType.Convert)
                      {
                          throw new Exception("Something went wrong");
                      }
  
                      return memberExpression.Member;
                  default:
                      done = true;
                      break;
              }
          }
  
          throw new Exception("Something went wrong");
      }
  }
  </code></pre>
  
  <p>The actual comparer:</p>
  
  <pre><code>public static class TestEqualityComparer
  {
      public static bool MyEquals&lt;T&gt;(TestEqualityHelper&lt;T&gt; a, T b)
      {
          return DoMyEquals(a.Value, b, a.IgnoredProps);
      }
  
      private static bool DoMyEquals&lt;T&gt;(T a, T b,
          IEnumerable&lt;PropertyInfo&gt; ignoredProperties)
      {
          var t = typeof(T);
          IEnumerable&lt;PropertyInfo&gt; props;
  
          if (ignoredProperties != null &amp;&amp; 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 =&gt; f.GetValue(a, null).Equals(f.GetValue(b, null)));
      }
  }
  </code></pre>
  
  <p>That's basically it.</p>
  
  <p>And here are two test snippets, the first one works, the second one fails:</p>
  
  <pre><code>//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 =&gt; 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 =&gt; x.Id), s2));
  }
  </code></pre>
  
  <p>The problem is with the <code>Except</code> method in <code>DoMyEquals</code>.</p>
  
  <p>Properties returned by <code>FindProperty</code> are not equal to those returned by <code>Type.GetProperties</code>. The difference I spot is in <code>PropertyInfo.ReflectedType</code>.</p>
  
  <ul>
  <li><p>regardless to the type of my objects, <code>FindProperty</code> tells me that the reflected type is <code>Base</code>.</p></li>
  <li><p>properties returned by <code>Type.GetProperties</code> have their <code>ReflectedType</code> set to <code>Base</code> or <code>Derived</code>, depending on the type of actual objects.</p></li>
  </ul>
  
  <p>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 <code>Ignore(x=&gt;x.Some.Deep.Property)</code>, so it probably will not do.</p>
  
  <p>Any suggestion on how to compare <code>PropertyInfo</code>'s or how to retrieve them from lambdas properly would be appreciated.</p>