Хочу создать аналог предиката как в list.Where(x => ReferenceEquals(x.barObj, likeObj.barObj));
class Bar {...}
class Baz {...}
class Foo
{
// Разные экземпляры Foo могут содержать barObj или bazObj ссылающиеся на один и тот же объектpublic Bar barObj = new Bar();
public Baz bazObj = new Baz();
}
public List<Foo> list = ...
public Foo likeObj = ...
public Func<Foo, bool> FilterPredicate(propName)
{
// Пробую воссоздать чтобы было так: x => ReferenceEquals(x.barObj, likeObj.barObj);var listItem = Expression.Parameter(typeof(Foo), "x");
// Как в Expression записать ссылку на внешний объект likeObj? Я сделал через Expression.Constant, но не уверен, что это правильно, хотя работает.var likeItem = Expression.Constant(likeObj);
var equals = Expression.Call(typeof(object), "ReferenceEquals", null, new Exception[] {
Expression.Field(listItem, propName),
Expression.Field(likeItem, propName)
});
var lambda = Exception.Lambda<Func<Foo, bool>>(equals, listItem);
return lambda.Compile();
}
...
// Потом использовать так
list.Where(FilterPredicate("barObj"));
// или
list.Where(FilterPredicate("bazObj"));
Здравствуйте, rFLY, Вы писали:
FLY>Хочу создать аналог предиката как в list.Where(x => ReferenceEquals(x.barObj, likeObj.barObj));
FLY> // Как в Expression записать ссылку на внешний объект likeObj? Я сделал через Expression.Constant, но не уверен, что это правильно, хотя работает.
так делать не стоит, лучше сделать делегат с двумя параметрами (см. ниже):
public class Bar {}
public class Baz {}
public class Foo
{
public Foo(string name, Bar barObj, Baz bazObj)
{
Name = name;
BarObj = barObj;
BazObj = bazObj;
}
public string Name { get; }
public Bar BarObj { get; }
public Baz BazObj { get; }
public override string ToString()
{
return $"{nameof(Name)}: {Name}"; // просто для удобства тестирования
}
}
public static class FilterBuilder
{
// ссылка на метод ReferenceEquals резолвится один раз и кэшируетсяprivate static readonly MethodInfo ReferenceEqualsMethod =
typeof(object).GetMethod(nameof(ReferenceEquals), BindingFlags.Static | BindingFlags.Public)
?? throw new InvalidOperationException();
// создает делегат с двумя параметрами - объектами для сравненияpublic static Func<Foo, Foo, bool> CreateFilter(string name)
{
var leftPar = Expression.Parameter(typeof(Foo), "left");
var rightPar = Expression.Parameter(typeof(Foo), "right");
var body = Expression.Call(ReferenceEqualsMethod,
Expression.PropertyOrField(leftPar, name),
Expression.PropertyOrField(rightPar, name));
var lambda = Expression.Lambda<Func<Foo, Foo, bool>>(body, leftPar, rightPar);
var predicate = lambda.Compile();
return predicate;
}
}
и потом соответственно использование:
var bar1 = new Bar();
var bar2 = new Bar();
var baz1 = new Baz();
var baz2 = new Baz();
List<Foo> fooList = new()
{
new Foo("obj1", bar1, baz1),
new Foo("obj2", bar1, baz2),
new Foo("obj3", bar2, baz1),
new Foo("obj4", bar2, baz2),
};
var likeObj = new Foo("like", bar1, baz1);
// фильтры лучше положить в переменные, иначе они будут компилироваться для каждого элемента коллекции, что просадит быстродействиеvar filter1 = FilterBuilder.CreateFilter(nameof(Foo.BarObj));
var filter2 = FilterBuilder.CreateFilter(nameof(Foo.BazObj));
Console.WriteLine(String.Join(", ", fooList.Where(x => filter1(likeObj, x))));
Console.WriteLine(String.Join(", ", fooList.Where(x => filter2(likeObj, x))));
Здравствуйте, JohnnyJ, Вы писали:
JJ>так делать не стоит, лучше сделать делегат с двумя параметрами (см. ниже):
А поподробнее можно почему так делать не стоит?
У Foo свойств, по которым должен быть фильтр, 6 штук и заранее не известно по какому нужно будет его применить (надо было сразу наверное сказать, но не хотел перегружать лишней информацией). Определять по какому применять фильтр планировал так:
enum FilterProp { barObj, bazObj, ..., booObj}
...
public void DoSomething(FilterProp filterProp)
{
var query = list.Where(FilterPredicate(filterProp.ToString()));
...
}
Если же создавать делегаты, как ты предлагаешь, то и Expression уже лишний (я не планирую вместо likeObj использовать другой объект для сравнения) и можно просто написать:
Func<Foo, bool> filter1 = x => ReferenceEquals(likeObj.barObj, x.barObj);
Так ведь или я что-то упускаю? А потом в свитче определять какой делегат использовать или сохранить делегаты в справочнике и доставать их по ключу.
Но хотелось избежать всего этого и решить одним Expression. Или построение и компиляция Expression при каждом фильтре слишком затратно будет?
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>"Внешний объект" по правильному называется closure.
Так то оно так, но как все же правильно привести объект к Expression чтобы его использовать в Expression.Call и в результате получить:
Func<Foo, bool> filter1 = x => ReferenceEquals(likeObj.barObj, x.barObj);
Здравствуйте, rFLY, Вы писали:
FLY>Так то оно так, но как все же правильно привести объект к Expression чтобы его использовать в Expression.Call и в результате получить:
Здравствуйте, rFLY, Вы писали:
FLY>А поподробнее можно почему так делать не стоит?
замыкание — это милая зверушка, но которая может потенциально вырасти в злобного монстра — неприятные побочные эффекты типа утечки памяти.
FLY>У Foo свойств, по которым должен быть фильтр, 6 штук и заранее не известно по какому нужно будет его применить (надо было сразу наверное сказать, но не хотел перегружать лишней информацией). Определять по какому применять фильтр планировал так: FLY>
FLY>Если же создавать делегаты, как ты предлагаешь, то и Expression уже лишний (я не планирую вместо likeObj использовать другой объект для сравнения) и можно просто написать: FLY>
FLY>Func<Foo, bool> filter1 = x => ReferenceEquals(likeObj.barObj, x.barObj);
FLY>
FLY>Так ведь или я что-то упускаю? А потом в свитче определять какой делегат использовать или сохранить делегаты в справочнике и доставать их по ключу. FLY>Но хотелось избежать всего этого и решить одним Expression. Или построение и компиляция Expression при каждом фильтре слишком затратно будет?
не то чтобы это совсем уж лишняя информация
при одном типе и 6 свойствах я бы не стал расчехлять мега-гаубицу, а просто сделал бы метод и switch без всяких делегатов:
Здравствуйте, rFLY, Вы писали:
FLY>Хочу создать аналог предиката как в list.Where(x => ReferenceEquals(x.barObj, likeObj.barObj));
Дам тебе универсальный совет. Напиши этот код на Шарпе, а потом декомпельни его ILSpy-ем. В нем можно установить версию языка. Если установить версию до той, что поддерживала Expression tree, ты получишь декомпилят, который после мелких доработок сможешь использовать в своём коде.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, JohnnyJ, Вы писали:
JJ>замыкание — это милая зверушка, но которая может потенциально вырасти в злобного монстра — неприятные побочные эффекты типа утечки памяти.
Об я по js знаю. Но в данном случае я не вижу проблем.
JJ>не то чтобы это совсем уж лишняя информация
В рамках моего вопроса она все же лишняя. Да и задачка посложнее чем where с одним условием. Если ее выложить полностью, боюсь мало кто бы стал вчитываться, скорее пройдут мимо.
JJ>при одном типе и 6 свойствах я бы не стал расчехлять мега-гаубицу
Гаубица — это написание Expression, его компиляция или еще что?
JJ>, а просто сделал бы метод и switch без всяких делегатов:
Как я уже сказал все несколько сложнее, по этому я и решил попробовать через Expression. Но если интересно вот листинг с 3-мя вариантами решения (он опять же не полный, но примерно обрисовывает задачу)
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Expression.Constant нормально
Я почему спросил. Когда при помощи Expression.DebugView вывел законченное выражение и сравнил его с выражением предиката, то получил:
Здравствуйте, rFLY, Вы писали:
FLY>Здравствуйте, JohnnyJ, Вы писали:
JJ>>замыкание — это милая зверушка, но которая может потенциально вырасти в злобного монстра — неприятные побочные эффекты типа утечки памяти. FLY>Об я по js знаю. Но в данном случае я не вижу проблем.
Да, пока проблем нет, я больше имел в виду общий случай — замыкания потенциально могут привести к проблемам.
Ну и как подсказывает опыт, лучше вообще с ними не связываться в динамических лямбдах, это дает 100% гарантию отсутствия проблем в будущем
JJ>>не то чтобы это совсем уж лишняя информация FLY>В рамках моего вопроса она все же лишняя. Да и задачка посложнее чем where с одним условием. Если ее выложить полностью, боюсь мало кто бы стал вчитываться, скорее пройдут мимо.
JJ>>при одном типе и 6 свойствах я бы не стал расчехлять мега-гаубицу FLY>Гаубица — это написание Expression, его компиляция или еще что?
да, гаубица — это Expression и компиляция.
Динамическая компиляция лямбд не самая очевидная штука — в простых случаях не нужна, в сложных — мощно, но требует тщательности в написании и очень плотного покрытия тестами.
Т.е. например в наличии гибкая структура данных, описываемая пользователем самостоятельно, и необходимость конструирования запросов пользователем же в рантайме.
Или свой DSL с движком по обработке данных по сильно кастомной бизнес-логике.
Второй вариант — именно то, с чем мне приходится иметь дело на основной работе. От лямбд стараемся сейчас отказаться в пользу генератора кода — есть набор правил на собственном языке, из него генерится пачка классов с известным интерфейсом, а в рантайме просто через интерфейс запихивается входной объект и получается результат.
JJ>>, а просто сделал бы метод и switch без всяких делегатов: FLY>Как я уже сказал все несколько сложнее, по этому я и решил попробовать через Expression. Но если интересно вот листинг с 3-мя вариантами решения (он опять же не полный, но примерно обрисовывает задачу)
глянул код — не самая очевидная структура данных, не совсем понял зачем шарить инстансы объектов Field. Если Record — просто динамическая структура, то словарика вроде должно хватать, или?
В целом вариант с лямдами хорош всем, кроме одного — перфоманс компиляции. Если поиск операция не частая, то и проблемы нет как таковой.
Также важны подробности применения и перспективы роста количества юзкейсов — завтра понадобится поиск по двум колонкам, сравнение без учета регистра для строковых полей, сравнение DateTime без учета времени — реальная жизнь и хотелки пользователей безграничны
В целом тема интересная, если не хочется разворачивать подробности здесь — стучись в личку, обсудим.