C # LINQ - оператор Select, который сравнивает все свойства класса с другим экземпляром того же класса? - PullRequest
2 голосов
/ 01 июля 2011

У меня есть следующее:

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 ]

2 голосов
/ 02 июля 2011

Вы можете либо реализовать IComparable (как предложил Оскар), либо вам нужно использовать отражение.

Если вы используете отражение и вам нужно ускорить код, то вы можете испускать динамический IL ( System.Reflection.Emit ) во время выполнения. См. Dapper source для примера динамической генерации IL.

1 голос
/ 02 июля 2011

Если это для 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);
}

Вы также можете использовать это с LINQ-to-Objects, если сначала используете .AsQueryable().

Например,

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
0 голосов
/ 02 июля 2011

Это маршрут 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 типы «знают» себя лучше, чем любые другие классы , на них должна быть возложена ответственность за обеспечение реализации сравнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...