Я пытаюсь создать метод в базовом классе, способный принимать параметры и создавать Predicate<T>
на лету.
Вот абстрактный класс:
public abstract class Table<TResults>
where TResults : class
{
...
protected abstract List<TResults> Results { get; set; }
...
}
А вот один класс, реализующий Table<TResults>
:
public class TrStudent
{
...
public string Name => // some code
...
public void Check()
{
// check implementation
}
}
public class TableStudents : Table<TrStudent>
{
...
protected override List<TrStudent> Results { get; set; }
...
public void Check_Student(string studentName) => Results.Find(r => r.Name == studentName).Check();
}
А вот еще один класс, реализующий Table<TResults>
:
public class TrAnswer
{
...
public string Name => // some code
public int Id => // some other code
...
public void Report()
{
// report implementation
}
}
public class TableAnswers<TrAnswer> : Table<TrAnswer>
{
...
protected override List<TrAnswer> Results { get; set; }
...
public void Report_Answer(string answerName, int answerId) => Results.Find(r => r.Name == answerName && r.Id == answerId).Report();
...
}
Что я хотел бы сделать, если это возможно, так это обновить класс Table<TResults>
до:
public abstract class Table<TResults>
where TResults : class
{
...
protected abstract List<TResults> Results { get; set; }
...
protected abstract Predicate<T> Predicate { get; }
protected T Find(parameters) => Results.Find(parameters, Predicate);
}
Поэтому я могу обновить производные классы до:
public class TableStudents : Table<TrStudent>
{
...
public void Check_Student(string studentName) => Find(studentName).Check();
}
public class TableAnswers<TrAnswer> : Table<TrAnswer>
{
...
public void Report_Answer(string answerName, int answerId) => Find(answerName, answerId).Report();
}
Но я не уверен, возможно ли это, так как некоторые лямбды принимают больше параметров, чем другие.
Я проверил Предикат , Лямбда-выражения , а также Класс выражений и почти уверен, что это можно сделать, но не знаю, с чего начать.
Спасибо за ваше время.
Привет, Дэйв, хорошо, это помогает, но я не уверен, как адаптировать его к тому, что я ищу. Спасибо.
Следующее не вариант?
public abstract class Table<TResults> where TResults : class {
// ...
protected TResults Find(Predicate<TResults> predicate)
=> Results.Find(predicate);
}
public class TableStudents : Table<TrStudent> {
// ...
public void Check_Student(string studentName)
=> Find(r => r.Name == studentName).Check();
}
public class TableAnswers<TrAnswer> : Table<TrAnswer>
{
// ...
public void Report_Answer(string answerName, int answerId)
=> Find(r => r.Name == answerName && r.Id == answerId).Report();
}
Я не вижу смысла в том, чтобы создавать Predicate<T>
во время выполнения.
Вообще ваше решение выглядит слишком сложным, если честно. Какой смысл абстрагироваться List.Find()
, когда каждый подкласс все равно должен переопределять List
результатов?
Эти подклассы буквально имеют все необходимое для фильтрации своих результатов (то есть сами результаты и параметры предиката), но все же они должны просить абстрактный базовый класс отфильтровать их?
Если вам нужен предикат несколько раз, вы можете использовать приватную функцию в каждом подклассе, которая возвращает Predicate<TResults>
для заданных параметров:
public class TableStudents : Table<TrStudent> {
// ...
public void Check_Student(string studentName)
=> Find(FilterBy(studentName)).Check();
private static Predicate<TrStudent> FilterBy(string studentName)
=> r => r.Name == studentName;
}
public class TableAnswers<TrAnswer> : Table<TrAnswer>
{
// ...
public void Report_Answer(string answerName, int answerId)
=> Find(FilterBy(answerName, answerId)).Report();
private static Predicate<TrAnswer> FilterBy(string answerName, int answerId)
=> r => r.Name == answerName && r.Id == answerId;
}
Не чувствуйте себя обязанным извлекать эти частные FilterBy()
функции в базовый класс только потому, что они имеют идентичные имена и работают одинаково. То, как подкласс фильтрует, не относится к базовым классам. Подкласс лучше знает, как фильтровать свои результаты, и он может использовать или не использовать одну или несколько частных функций для создания необходимых ему Predicate<T>
.
Обратите внимание, что FilterBy()
— это функция, которая возвращает Predicate<T>
. Predicate<T>
— это функциональный объект, который возвращает bool
, когда вы присваиваете ему значение T
.
Это похоже на обычную функцию, такую как bool MyPredicate(T value) {...}
, только вы можете хранить ее в переменных, передавать и даже возвращать из других функций:
// create function object
var isAlphaNumeric = new Predicate<char>(c => char.IsLetter(c) || char.IsDigit(c));
// call function object with some values
Debug.Assert(isAlphaNumeric('a') == true);
Debug.Assert(isAlphaNumeric('&') == false);
Эта более подробная версия FilterBy()
может прояснить связь с isAlphaNumeric
:
private static Predicate<TrStudent> FilterBy(string studentName) {
var hasName = new Predicate<TrStudent>(r => r.Name == studentName);
return hasName;
}
Основное различие между isAlphaNumeric
и hasName
заключается в том, что hasName
необходимо захватить значение параметра studentName
, сохранив его внутри объекта функции. Позже, когда возвращаемый объект функции hasName
вызывается List.Filter()
с одним или несколькими объектами TrStudent
, это значение будет доступно для сравнения имен.
Кстати, функции, возвращающие функцию (или принимающие в качестве параметров другие функции) — это так называемые функции высшего порядка. C# взял их из функционального программирования, и они очень мощные. Например, LINQ был бы невозможен без них. Но они также могут заменить некоторые шаблоны объектно-ориентированного проектирования, такие как шаблон стратегии, шаблон метода шаблона, фабричные шаблоны и даже внедрение зависимостей.
Привет, спокойной ночи, Nerd Pride, проблема в том, что каждый Table<TResults> имеет множество методов, которые снова и снова используют один и тот же Predicate<T>, поэтому я хотел сохранить его СУХИМ. Когда я начал пытаться решить эту проблему, я думал, что это будет намного проще, чем оказалось. Однако сейчас я просто хотел научиться это делать. Спасибо.
Кстати, DRY переоценен. В долгосрочной перспективе дублирование гораздо проще поддерживать, чем сложные проекты, которые пытаются извлечь каждое маленькое дублирование :)
если бы я мог спросить вас, как private static Predicate<TrAnswer> FilterBy(string answerName, int answerId) => r => r.Name == answerName && r.Id == answerId;
имеет доступ к «r»? Я не понимаю магии этой части. Спасибо!
Отвечает ли это на ваш вопрос? Динамически генерировать предикат во время выполнения