elgato
elgato

Reputation: 555

How to create a Predicate<T> dynamically on runtime

I am trying to create a method in a base class capable of taking parameters and creating a Predicate<T> on the fly.

Here is the abstract class:

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

And here is one class implementing 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();        
}

And here is another class implementing 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();
    ...
}

What I would like to do, if possible is update the Table<TResults> class to:

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

So I can update the derived classes to:

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

But I am not sure if this is possible as some lambdas take more parameters than others.

I have checked Predicate, Lambda expressions and also Expression class and I am almost sure it could be done, but I dont know where to start.

Thanks for your time.

Upvotes: 0

Views: 671

Answers (1)

Good Night Nerd Pride
Good Night Nerd Pride

Reputation: 8452

Is the following not an option?

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

I don't see the point in going through the hazzle to create a Predicate<T> at runtime.


Generally your solution looks a little bit too over-engineered, to be honest. What's the point of abstracting List.Find() when each subclass has to override the List of results anyway?

Those subclasses literally have everything they need to filter their results (i.e., the results itself and the predicate parameters), but still they must ask the abstract base class to filter for them?


If you need the predicate multiple times you could use a private function in each subclass that returns the Predicate<TResults> for the given parameters:

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

Don't feel compelled to extract those private FilterBy() functions into the base class only because they got identical names and work similarly. How a subclass filters is none of the base classes business. The subclass knows best how to filter its results and it might or might not use one or more private functions to create the Predicate<T>s it needs.


Note that FilterBy() is a function that returns a Predicate<T>. A Predicate<T> is a function object that returns a bool when you give it a T value.

It's similar to a regular function like bool MyPredicate(T value) {...}, only that you can store it in variables, pass it around, and even return it from other functions:

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

This more verbose version of FilterBy() might make the relation to isAlphaNumeric more clear:

private static Predicate<TrStudent> FilterBy(string studentName) {
    var hasName = new Predicate<TrStudent>(r => r.Name == studentName);
    return hasName;
}

The major difference between isAlphaNumeric and hasName is that hasName needs to capture the value of the studentName parameter by storing it inside the function object. Later, when the returned hasName function object is called by List.Filter() with one or more TrStudent objects, this value will be available for the name comparison.


By the way, a function returning a function (or taking other functions as parameters) are so called higher-order functions. C# took them from functional programming and they are very powerful. For instance, LINQ wouldn't be possible without them. But they can also replace some object-oriented design patterns like the strategy pattern, the template method pattern, the factory patterns, and even dependency injection.

Upvotes: 1

Related Questions