У меня есть class X
, который реализует interface IX
.У меня также есть класс репозитория, выделенный для X, который использует лямбда-выражения в качестве параметров:
public interface IX
{
}
public class X : IX
{
....
}
public class XRepository : IRepository<X>
{
public IEnumerable<X> Filter(Func<X, bool> filterFunc)
{
...
}
}
Мне нужно, чтобы класс репозитория работал с интерфейсом IX
, поэтому я добавляю IRepository<IX>
к интерфейсамреализуется:
public class XRepository : IRepository<X>, IRepository<IX>
{
public IEnumerable<X> Filter(Func<X, bool> filterFunc)
{
...
}
public IEnumerable<IX> Filter(Func<IX, bool> filterFunc)
{
// I need to call the same filter method as above, but
// in order to do so I must convert the Func<IX, bool> to Func<X, bool>.
}
}
Я должен преобразовать Func<IX, bool>
в Func<X, bool>
, но, поскольку код написан на C # 3.0 с использованием .NET 3.5, я не могу воспользоваться ковариацией типов, которая была введена в 4.0.
Простым решением может быть использование Func<X, bool> newFunc = x => filterFunc(x);
, где filterFunc
имеет тип Func<IX, bool>
.Это скомпилирует, и можно ожидать, что он будет работать нормально, но я предполагаю, что это не будет.Проблема в том, что я использую стороннюю платформу для реализации фильтра, а именно FluentNhibernate.Я знаю, что он использует деревья выражений для удаления переданного в условие доступа к лямбда-выражению члена (например, x => x.Name == "John"
) для создания собственного запроса SQL (например, WHERE Name = 'John'
).Приведенное выше решение даст Func<X, bool>
, которое не является таким выражением, и я боюсь, что оно не сможет перевести.Поэтому мне нужно создать то же самое лямбда-выражение, но с совместимым типом.Зная, что X реализует IX, очевидно, что любой код внутри Func<IX, bool>
будет работать для объектов типа X
.Однако для меня не очевидно, как я могу выполнить это преобразование.
Я предполагаю, что это можно сделать с помощью деревьев выражений.Я также боюсь, что моя работа сильно пострадает.Даже если я решу найти другое решение для моего сценария, я все равно буду признателен за предложенный способ перевода одной лямбды в похожую другую.
Редактировать:
Чтобы прояснить проблему, с которой я столкнулся, я написал следующий тест, имитирующий реальный сценарий, с которым я столкнулся:
Func<IX, bool> filter = y => y.Name == "John";
Func<X, bool> compatibleFilter = y => filter(y);
...
// Inside the Filter(Func<X, bool> filter method)
using(var session = nhibernateSessionFactory.OpenSession())
{
IEnumerable<X> xx = session.Query<X>().Where(z => compatibleFilter(z)).ToList();
}
, поэтому при методе ToList()
я получаю следующее исключение
Unable to cast object of type 'NHibernate.Hql.Ast.HqlParameter' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'.
Это подтверждает мое предположение, что Flunet NHiberante не может правильно обрабатывать аргумент compatibilityFilter.
Итак, я хочу, чтобы это был способ преобразования Func в Func или, как предложил Джон Скит, Expression<Func<IX, bool>>
на Expression<Func<X, bool>>
, которые имеют одинаковое тело (y => y.Name = "John"
).
Редактировать 2:
Наконец-то я это сделал!Правильный способ - не использовать Func<X, bool>
, а Expression<Func<X, bool>>
.
Expression<Func<IX, bool>> filter = y => y.Name == "John Skeet";
Expression<Func<X, bool>> compatibleFilter = Expression.Lambda<Func<X, bool>>(
filter.Body,
filter.Parameters);
. Это дает правильный запрос SQL.IX, bool