Generic Predicates with Predicate<T>

Unlike the many variants of Action<>, the framework provides us with a single Predicate<T> type, which defines a delegate to a function that takes a single parameter of type T and returns a Boolean.

Note

Why only the one parameter? There are good computer-science-philosophical reasons for it. In mathematical logic, a predicate is usually defined as follows:

P : X { true, false }

That can be read as “a Predicate of some entity X maps to ‘true’ or ‘false’”. The single parameter in the mathematical expression is an important limitation, allowing us to build more complex systems from the simplest possible building blocks.

This formal notion gives rise to the single parameter in the .NET Predicate<T> class, however pragmatically useful it may be to have more than one parameter in your particular application.

We can delete our Check delegate (Hurrah! More code removed!), and replace it with a Predicate<T> that takes a Document as its type parameter:

Predicate<Document>

And we can update the DocumentProcessor to make use of Predicate<T>, as shown in Example 5-14.

Example 5-14. DocumentProcessor updated to use Predicate<T>

class DocumentProcessor
{
    class ActionCheckPair
    {
        public Action<Document> Action { get; set; }
        public Predicate<Document> QuickCheck { get; set; }
    }

    private readonly List<ActionCheckPair> processes =
        new List<ActionCheckPair>();

    public void AddProcess(Action<Document> action)
    {
        AddProcess(action, null);
    }

    public void AddProcess(Action<Document> action,
                           Predicate<Document> quickCheck)
    {
        processes.Add(
            new ActionCheckPair { Action = action, QuickCheck = quickCheck });
    }

    // ...
}

We can now update our client code to use our new DocumentProcessor API, calling AddProcess now that the list of processes is private (see Example 5-15).

Example 5-15. Updating Configure to use modified DocumentProcessor

static DocumentProcessor Configure()
{
    DocumentProcessor rc = new DocumentProcessor();
    rc.AddProcess(DocumentProcesses.TranslateIntoFrench);
    rc.AddProcess(DocumentProcesses.Spellcheck);
    rc.AddProcess(DocumentProcesses.Repaginate);

    TrademarkFilter trademarkFilter = new TrademarkFilter();
    trademarkFilter.Trademarks.Add("Ian");
    trademarkFilter.Trademarks.Add("Griffiths");
    trademarkFilter.Trademarks.Add("millennium");

    rc.AddProcess(trademarkFilter.HighlightTrademarks);

    return rc;
}

For the time being, we’re using the overload of AddProcess that doesn’t supply a quickCheck, so if we compile and run, we get the same output as before:

Processing document 1
Document traduit.
Spellchecked document.
Repaginated document.

Processing document 2
Document traduit.
Spellchecked document.
Repaginated document.
Highlighting 'millennium'

OK, the idea here was to allow our production team to quickly configure a check to see if the process was likely to fail, before embarking on a resource-intensive task. Let’s say DocumentProcesses.TranslateIntoFrench is a very time-consuming function, and they’ve discovered that any document whose text contains a question mark (?) will fail.

They’ve raised a bug with the machine translation team, but they don’t want to hold up the entire production process until it is fixed—only 1 in 10 documents suffer from this problem.

They need to add a quick check to go with the TranslateIntoFrench process. It is only one line of code:

return !doc.Contains("?");

They could create a static class, with a static utility function to use as their predicate, but the boilerplate code would be about 10 times as long as the actual code itself. That’s a barrier to readability, maintenance, and therefore the general well-being of the developer. C# comes to our rescue with a language feature called the anonymous method.