如何在运行时动态创建 Predicate<T>

How to create a Predicate<T> dynamically on runtime

我正在尝试在基础 class 中创建一个能够获取参数并动态创建 Predicate<T> 的方法。

这里是摘要class:

public abstract class Table<TResults>
    where TResults : class
{
    ...
    protected abstract List<TResults> Results { get; set; }
    ...
}

这是一个 class 实施 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();        
}

这是另一个 class 实现 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> class 更新为:

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);
}

所以我可以将派生的 classes 更新为:

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();
}

但我不确定这是否可行,因为一些 lambda 比其他的接受更多的参数。

我已经检查过Predicate, Lambda expressions and also Expression class,我几乎可以肯定它可以完成,但我不知道从哪里开始。

感谢您的宝贵时间。

以下不是选项吗?

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> 有什么意义。


老实说,通常您的解决方案看起来有点过度设计。当每个子 class 无论如何都必须覆盖结果的 List 时,抽象 List.Find() 有什么意义?

那些子class确实拥有过滤结果所需的一切(即结果本身和谓词参数),但他们仍然必须要求抽象基础class过滤他们?


如果你多次需要谓词,你可以在每个子class中使用私有函数,returns 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() 函数具有相同的名称和相似的工作方式而被迫将它们提取到基础 class 中。 class 基础 classes 业务的子class 过滤器是如何运作的。 subclass 最了解如何过滤其结果,它可能会也可能不会使用一个或多个私有函数来创建它需要的 Predicate<T>s。


请注意 FilterBy() 是 return 一个 Predicate<T> 的函数。 Predicate<T> 是一个函数对象,当您给它一个 T 值时,它 return 是一个 bool

它类似于像 bool MyPredicate(T value) {...} 这样的常规函数​​,只是您可以将它存储在变量中,传递它,甚至 return 它来自其他函数:

// 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;
}

isAlphaNumerichasName的主要区别在于hasName需要捕获studentName参数的值通过将其存储在函数对象中。稍后,当 returned hasName 函数对象被 List.Filter() 调用一个或多个 TrStudent 对象时,该值将可用于名称比较。


顺便说一句,一个函数return调用一个函数(或将其他函数作为参数)被称为higher-order functions。 C# 从函数式编程中提取了它们,它们非常强大。例如,没有它们就不可能有 LINQ。但它们也可以替代一些面向对象的设计模式,如策略模式、模板方法模式、工厂模式,甚至依赖注入。