Playing With The Predicate Delegate

Posted on September 12, 2007 @ 02:55 csharp

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  public interface IQuestion 
  {
      ICategory Category { get; }
      string Text { get; }
  }

  public interface ICategory 
  {
      string Name { get; }
  }

  public interface IQuestionBank 
  {
      IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate );
  }

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  public class QuestionBank : IQuestionBank 
  {
      public QuestionBank( ) 
      {
          this.questions = new List< IQuestion >( );
          this.questions.Add( new Question( Category.English, "Do you like English?" ) );
          this.questions.Add( new Question( Category.Math, "What is 2 + 2?" ) );
          this.questions.Add( new Question( Category.English, "Can you spell?" ) );
          this.questions.Add( new Question( Category.Math, "What is 3 + 3?" ) );
      }        

      public IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate ) 
      {
          foreach ( IQuestion question in this.questions ) 
          {
              if ( predicate( question ) ) 
                  yield return question;
          }
      }

      IList<IQuestion> questions;
  }

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  public class PredicateDelegateFactory 
  {
      public static Predicate< IQuestion > EnglishFilter =
          delegate( IQuestion question ) { return question.Category.Equals( Category.English ); };

      public static Predicate< IQuestion > MathFilter =
          delegate( IQuestion question ) { return question.Category.Equals( Category.Math ); };
  }

  public class Bootstrap 
  {
      public static void Main( ) 
      {
          IQuestionBank questionBank = new QuestionBank( );

          Console.Out.WriteLine( "Searching for all English questions..." );
          foreach ( IQuestion question in questionBank.FindBy( PredicateDelegateFactory.EnglishFilter ) ) 
          {
              Console.Out.WriteLine( question );
          }

          Console.Out.WriteLine( );
          Console.Out.WriteLine( "Searching for all Math questions..." );
          foreach ( IQuestion question in
              questionBank.FindBy( delegate( IQuestion item ) { return item.Category.Equals( Category.Math ); } ) ) {
              Console.Out.WriteLine( question );
          }

          Console.In.ReadLine( );
      }
  }

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
  //public delegate bool Predicate< T >( T obj );

  public interface IQuestion 
  {
      ICategory Category { get; }
      string Text { get; }
  }

  public interface ICategory 
  {
      string Name { get; }
  }

  public interface IQuestionBank 
  {
      IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate );
  }

  public class Question : IQuestion 
  {
      public Question( ICategory category, string text ) 
      {
          _category = category;
          _text = text;
      }

      public ICategory Category 
      {
          get { return _category; }
      }

      public string Text 
      {
          get { return _text; }
      }

      public override string ToString( ) 
      {
          return Text;
      }

      private readonly ICategory _category;
      private readonly string _text;
  }

  public class Category : ICategory, IEquatable< Category > 
  {
      private Category( string name ) 
      {
          _name = name;
      }

      public string Name 
      {
          get { return _name; }
      }

      public static readonly ICategory Math = new Category( "Math" );
      public static readonly ICategory English = new Category( "English" );

      public bool Equals( Category category ) 
      {
          if ( category == null ) 
          {
              return false;
          }
          return Equals( _name, category._name );
      }

      public override bool Equals( object obj ) 
      {
          if ( ReferenceEquals( this, obj ) ) 
          {
              return true;
          }
          return Equals( obj as Category );
      }

      public override int GetHashCode( ) 
      {
          return _name.GetHashCode( );
      }

      private readonly string _name;
  }

  public class QuestionBank : IQuestionBank 
  {
      public QuestionBank( ) 
      {
          _questions = new List< IQuestion >( );
          _questions.Add( new Question( Category.English, "Do you like English?" ) );
          _questions.Add( new Question( Category.Math, "What is 2 + 2?" ) );
          _questions.Add( new Question( Category.English, "Can you spell?" ) );
          _questions.Add( new Question( Category.Math, "What is 3 + 3?" ) );
      }

      public IEnumerable< IQuestion > FindBy( Predicate< IQuestion > predicate ) 
      {
          foreach ( IQuestion question in _questions ) 
          {
              if ( predicate( question ) ) 
              {
                  yield return question;
              }
          }
      }

      private readonly IList< IQuestion > _questions;
  }

  public class PredicateDelegateFactory 
  {
      public static Predicate< IQuestion > EnglishFilter =
          delegate( IQuestion question ) { return question.Category.Equals( Category.English ); };

      public static Predicate< IQuestion > MathFilter =
          delegate( IQuestion question ) { return question.Category.Equals( Category.Math ); };
  }

  public class Bootstrap 
  {
      public static void Main( ) 
      {
          IQuestionBank questionBank = new QuestionBank( );

          Console.Out.WriteLine( "Searching for all English questions..." );
          foreach ( IQuestion question in questionBank.FindBy( PredicateDelegateFactory.EnglishFilter ) ) 
          {
              Console.Out.WriteLine( question );
          }

          Console.Out.WriteLine( );
          Console.Out.WriteLine( "Searching for all Math questions..." );
          foreach ( IQuestion question in
              questionBank.FindBy( delegate( IQuestion item ) { return item.Category.Equals( Category.Math ); } ) ) {
              Console.Out.WriteLine( question );
          }

          Console.In.ReadLine( );
      }
  }

PlayingWithPredicates.txt (3.2 KB)