score:2

You can either implement IComparable (as Oskar suggested) or you need to use reflection.

If you're using reflection and you need to speed-up your code then you can emit dynamic IL (System.Reflection.Emit) at run-time. See Dapper source for an example of dynamic IL generation.

score:0

This is the Reflection route (using LINQ to Objects):

static readonly IEnumerable<PropertyInfo> otherProps = typeof (TOther).GetProperties();

public void DoSomething (TOther thatBar)
{
    var foo = Items
        .Select (item => item.Bar)
        .Single (thisBar =>
            otherProps.All (prop =>
                prop.GetValue (thisBar, null).Equals (
                   prop.GetValue (thatBar, null)
                )
            )
        );

}

And be sure to watch for corner cases (e.g. indexer properties, properties you don't want to match, set-only properties, properties that can throw exceptions).

I didn't check if it compiles though so you might want to tweak the method so it does the right thing.

However, you should not do that if:

  • you need this in more than one place;
  • Items is not an in-memory list or array but a query result, as in LINQ to SQL;
  • you need to do it considerably fast.

After all, according to Information Expert principle, since concrete Bar types “know” themselves better than any other classes, they should be given the responsibility to provide a comparison implementation.

score:1

If this is for LINQ-to-SQL etc, maybe something like:

static Expression<Func<T,bool>> GetComparer<T>(T obj)
{
    var c = Expression.Constant(obj, typeof(T));
    var param = Expression.Parameter(typeof(T), "x");
    var members = (
        from member in typeof(T).GetMembers(BindingFlags.Instance | BindingFlags.Public)
        where member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property
        select Expression.Equal(Expression.MakeMemberAccess(c, member),
            Expression.MakeMemberAccess(param, member))).ToList();
    Expression body;
    if(members.Count == 0) body = Expression.Constant(true, typeof(bool));
    else body = members.Aggregate((x,y) => Expression.AndAlso(x,y));
    return Expression.Lambda<Func<T,bool>>(body, param);
}

You can use that with LINQ-to-Objects too if you use .AsQueryable() first.

For example,

class Test
{
    public int Foo { get; set; }
    public string Bar { get; set; }
}
static void Main()
{
    var data = new[] {
        new Test { Foo = 1, Bar = "a"}, new Test { Foo = 1, Bar = "b"},
        new Test { Foo = 2, Bar = "a"}, new Test { Foo = 2, Bar = "b"},
        new Test { Foo = 1, Bar = "a"}, new Test { Foo = 1, Bar = "b"},
        new Test { Foo = 2, Bar = "a"}, new Test { Foo = 2, Bar = "b"},
    };
    var findMe = new Test { Foo = 1, Bar = "b" };
    var found = data.AsQueryable().Where(GetComparer(findMe)).ToList();
    // finds 2 items, as expected
}

or alternatively, via .Compile():

    var found = data.Where(GetComparer(findMe).Compile()).ToList();
    // finds 2 items, as expected

Related Articles