C#LINQ - 选择语句,将类的所有属性与同一类的不同实例进行比较?

时间:2011-07-01 19:55:19

标签: c# linq reflection

我有以下内容:

IEnumerable<Foo<T, TOther>> Items { get; set; }

public class Foo<T, TOther>
{
    public TOther Bar { get; }

    //Somewhere in the class Bar is generated/populated
}


public void DoSomething(TOther bar)
{
    var foo = Items.Single(item => //Where all properties of item.Bar match bar);
}

那么,是否有一种很好的LINQ方式可以动态地比较item.Bar的所有属性和bar的属性?或者我会被反射困住?

3 个答案:

答案 0 :(得分:2)

您可以实施IComparable(如Oskar建议的那样),或者您需要使用反射。

如果您正在使用反射并且需要加速代码,则可以在运行时发出动态IL(System.Reflection.Emit)。有关动态IL生成的示例,请参阅Dapper源。

答案 1 :(得分:1)

如果这是用于LINQ-to-SQL等,可能类似于:

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);
}

如果先使用.AsQueryable(),也可以将它与LINQ-to-Objects一起使用。

例如,

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
}

或者,通过.Compile()

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

答案 2 :(得分:0)

这是Reflection路由(使用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)
                )
            )
        );

}

请务必留意极端情况(例如索引器属性,您不想匹配的属性,仅设置属性,可以抛出异常的属性)。

我没有检查它是否编译,所以你可能想要调整方法,以便做正确的事。

然而,如果出现以下情况,应执行此操作:

  • 你需要在不止一个地方;
  • Items不是内存列表或数组,而是查询结果,如LINQ to SQL;
  • 你需要相当快地完成它。

毕竟,根据Information Expert原则,由于具体Bar类型“知道”自己比任何其他类更好,因此他们应该负责提供比较实施