While digging through the .NET Framework I came across another sweet little delegate called “The Predicate Delegate.” Say it with me now, “Predicate Delegate”. It’s got a nice ring to it, don’t you think? It’s signature looks like this…

1 public delegate bool Predicate<T>(T obj);

So let’s play with another contrived example. Let’s pretend I’ve got a question bank full of questions. And each question belongs to a particular category, and I need to be able to retrieve a set of questions by category.

 1 public interface IQuestion 
 2   {
 3       ICategory Category { get; }
 4       string Text { get; }
 5   }
 6 
 7   public interface ICategory 
 8   {
 9       string Name { get; }
10   }
11 
12   public interface IQuestionBank 
13   {
14       IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate );
15   }

Take a look at the IQuestionBank.FindBy method. Notice that it takes in a Predicate delegate as it’s input parameter, and returns a set of IQuestion’s. Let’s take a look at how the FindBy method could be implemented.

 1 public class QuestionBank : IQuestionBank 
 2   {
 3       public QuestionBank( ) 
 4       {
 5           this.questions = new List< IQuestion >( );
 6           this.questions.Add( new Question( Category.English, "Do you like English?" ) );
 7           this.questions.Add( new Question( Category.Math, "What is 2 + 2?" ) );
 8           this.questions.Add( new Question( Category.English, "Can you spell?" ) );
 9           this.questions.Add( new Question( Category.Math, "What is 3 + 3?" ) );
10       }        
11 
12       public IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate ) 
13       {
14           foreach ( IQuestion question in this.questions ) 
15           {
16               if ( predicate( question ) ) 
17                   yield return question;
18           }
19       }
20 
21       IList<IQuestion> questions;
22   }

The FindBy method is iterating through the internal list of questions and only returning the questions that match the predicate’s criteria defined by the client of the code. This means that the client can define an anonymous method to do the filtering, you could use a strategy to interchange filtering algorithms at runtime to produce dynamic querying.

Here’s a couple of different ways you could perform the querying. One uses the pre-defined delegate for filtering all the English questions, and the other uses an anonymous delegate to define the filter criteria.

 1 public class PredicateDelegateFactory 
 2   {
 3       public static Predicate< IQuestion > EnglishFilter =
 4           delegate( IQuestion question ) { return question.Category.Equals( Category.English ); };
 5 
 6       public static Predicate< IQuestion > MathFilter =
 7           delegate( IQuestion question ) { return question.Category.Equals( Category.Math ); };
 8   }
 9 
10   public class Bootstrap 
11   {
12       public static void Main( ) 
13       {
14           IQuestionBank questionBank = new QuestionBank( );
15 
16           Console.Out.WriteLine( "Searching for all English questions..." );
17           foreach ( IQuestion question in questionBank.FindBy( PredicateDelegateFactory.EnglishFilter ) ) 
18           {
19               Console.Out.WriteLine( question );
20           }
21 
22           Console.Out.WriteLine( );
23           Console.Out.WriteLine( "Searching for all Math questions..." );
24           foreach ( IQuestion question in
25               questionBank.FindBy( delegate( IQuestion item ) { return item.Category.Equals( Category.Math ); } ) ) {
26               Console.Out.WriteLine( question );
27           }
28 
29           Console.In.ReadLine( );
30       }
31   }

This yields an output of….

Why is this useful?

This allows you to specify the criteria for querying data from the question repository without having to modify the IQuestionBank implementation. This closes the repository for modification but leaves it open for extension.

Design Principles

The full source for this example for your viewing pleasure!

  1 //public delegate bool Predicate< T >( T obj );
  2 
  3   public interface IQuestion 
  4   {
  5       ICategory Category { get; }
  6       string Text { get; }
  7   }
  8 
  9   public interface ICategory 
 10   {
 11       string Name { get; }
 12   }
 13 
 14   public interface IQuestionBank 
 15   {
 16       IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate );
 17   }
 18 
 19   public class Question : IQuestion 
 20   {
 21       public Question( ICategory category, string text ) 
 22       {
 23           _category = category;
 24           _text = text;
 25       }
 26 
 27       public ICategory Category 
 28       {
 29           get { return _category; }
 30       }
 31 
 32       public string Text 
 33       {
 34           get { return _text; }
 35       }
 36 
 37       public override string ToString( ) 
 38       {
 39           return Text;
 40       }
 41 
 42       private readonly ICategory _category;
 43       private readonly string _text;
 44   }
 45 
 46   public class Category : ICategory, IEquatable< Category > 
 47   {
 48       private Category( string name ) 
 49       {
 50           _name = name;
 51       }
 52 
 53       public string Name 
 54       {
 55           get { return _name; }
 56       }
 57 
 58       public static readonly ICategory Math = new Category( "Math" );
 59       public static readonly ICategory English = new Category( "English" );
 60 
 61       public bool Equals( Category category ) 
 62       {
 63           if ( category == null ) 
 64           {
 65               return false;
 66           }
 67           return Equals( _name, category._name );
 68       }
 69 
 70       public override bool Equals( object obj ) 
 71       {
 72           if ( ReferenceEquals( this, obj ) ) 
 73           {
 74               return true;
 75           }
 76           return Equals( obj as Category );
 77       }
 78 
 79       public override int GetHashCode( ) 
 80       {
 81           return _name.GetHashCode( );
 82       }
 83 
 84       private readonly string _name;
 85   }
 86 
 87   public class QuestionBank : IQuestionBank 
 88   {
 89       public QuestionBank( ) 
 90       {
 91           _questions = new List< IQuestion >( );
 92           _questions.Add( new Question( Category.English, "Do you like English?" ) );
 93           _questions.Add( new Question( Category.Math, "What is 2 + 2?" ) );
 94           _questions.Add( new Question( Category.English, "Can you spell?" ) );
 95           _questions.Add( new Question( Category.Math, "What is 3 + 3?" ) );
 96       }
 97 
 98       public IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate ) 
 99       {
100           foreach ( IQuestion question in _questions ) 
101           {
102               if ( predicate( question ) ) 
103               {
104                   yield return question;
105               }
106           }
107       }
108 
109       private readonly IList< IQuestion > _questions;
110   }
111 
112   public class PredicateDelegateFactory 
113   {
114       public static Predicate< IQuestion > EnglishFilter =
115           delegate( IQuestion question ) { return question.Category.Equals( Category.English ); };
116 
117       public static Predicate< IQuestion > MathFilter =
118           delegate( IQuestion question ) { return question.Category.Equals( Category.Math ); };
119   }
120 
121   public class Bootstrap 
122   {
123       public static void Main( ) 
124       {
125           IQuestionBank questionBank = new QuestionBank( );
126 
127           Console.Out.WriteLine( "Searching for all English questions..." );
128           foreach ( IQuestion question in questionBank.FindBy( PredicateDelegateFactory.EnglishFilter ) ) 
129           {
130               Console.Out.WriteLine( question );
131           }
132 
133           Console.Out.WriteLine( );
134           Console.Out.WriteLine( "Searching for all Math questions..." );
135           foreach ( IQuestion question in
136               questionBank.FindBy( delegate( IQuestion item ) { return item.Category.Equals( Category.Math ); } ) ) {
137               Console.Out.WriteLine( question );
138           }
139 
140           Console.In.ReadLine( );
141       }
142   }

PlayingWithPredicates.txt (3.2 KB)

comments powered by Disqus