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
.
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;}
publicPredicate<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() { DocumentProcessorrc =
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.