Закрытие в предикатах с делегатами, неизвестными во время компиляции - PullRequest
0 голосов
/ 16 сентября 2011

У меня есть следующий сценарий:

Метод, который получает список людей из определенного класса, которые короче некоторого порога высоты.Я использую метод расширения Где, чтобы отфильтровать их.Поскольку предикат зависит от некоторых внешних данных, переменные classId и height становятся частью лямбда-замыкания.

public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(p => p.Height <= height && p.ClassId == classId).ToList();
}

Проблема в том, что иногда предикат усложняется, поэтому я хочу извлечь его вдругой метод:

public List<Person> getPeopleFromClassLowerThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(isLowerThan).ToList();
}

private bool isLowerThan(Person person)
{

}

Однако закрытие здесь становится проблемой, потому что я не могу передать переменные в функцию предиката.Я могу сделать функцию делегата встроенной (не используя лямбда-выражения), чтобы предикат выглядел как функция, но тогда эта функция также должна быть в том же методе, что и вызов Where().В большинстве сценариев это нормально, , но иногда я хочу, чтобы внешняя функция (неизвестная во время компиляции) вызывалась как предикат Where .

. Самое близкое к этому решениеэто так:

public delegate bool ExtendedPredicate<T, TArg>(T argument, TArg[] arguments);

public static class Extended
{
    public static IEnumerable<T> Where<T, TArg>(this IEnumerable<T> collection, ExtendedPredicate<T, TArg> predicate, params TArg[] arguments)
    {
        foreach (T item in collection)
        {
            if (predicate(item, arguments))
                yield return item;
        }

        yield break;
    }
}

public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(isFromClassShorterThanLimit, classId, height).ToList();
}

public bool isFromClassShorterThanLimit(Person person, int[] arguments)
{
    return person.Height <= arguments[0] && person.ClassId == arguments[1];
}

но я нахожу это несколько узким, а также, для каждого метода расширения должна быть записана новая перегрузка.В дополнение ко всему этому, я думаю, EntityFramework не сможет преобразовать такие выражения в разумный оператор SQL (не уверен в этом, я не эксперт в том, как EF генерирует SQL из лямбд, но это дикая догадка). (переменное число аргументов может или не может быть предоставлено)

Вопрос:

То, что я хотел бы знать, есть ли какие-либоумный способ добиться этого с обычными делегатами (не заставляя определенные во время компиляции функции использовать в качестве предиката) без таких хаков, как показано выше?

Ответы [ 2 ]

2 голосов
/ 16 сентября 2011

Разве это не работает для вас?

public List<Person> getPeopleFromClassLowerThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(p => isLowerThan(p, classId, height)).ToList();
}

private bool isLowerThan(Person person, int classId, int height)
{

}
0 голосов
/ 16 сентября 2011

Почему бы просто не вызвать ваш метод как предикат нормального метода где:

public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(x=>isFromClassShorterThanLimit(classId, height)).ToList();
}
...