Поделиться через


Generic Collections II

In yesterday's post I went over the very basics of generic collections in the 2.0 .Net framework.  Today I pick up where I left off with...

Predicate<T>

System.Predicate<T> is a generic delegate that is used to specify a search condition method to pass to the generic collections.  It simply takes an object of type <T> and returns a true or false if that object meets a condition that the method expects.  The definition is quite simple:

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

Here's another (perhaps easier) way to explain it:  When you use the List<T>.Find() method, it needs to know what you wish to find.  It accomplishes this by taking a Predicate<T> parameter that it then calls with each element in the collection to essentially ask "Is this it?  No?  Is this it? ..."  Here's how List<T>.Find() actually looks in code:

 public T Find(Predicate<T> match)
{
   if (match == null)
   {
      // throw here...
   }

   // Iterate through the collection and return the first object that passes the specified predicate.
   for (int i = 0; i < this._size; ++i)
   {
      if (match(this._items[i]))
      {
         return this._items[i];
      }
   }

   // Nothing found, return the default object for this type.
   return default(T);
}

Pretty simple actually.  The one thing you may not have encountered here is default(T) .  This is a new construct with generics that returns null for reference types, zero for numeric value types, and structs with their fields initialized appropriately to null or zero.  The equivalent in C++ is to simply use T().  (note: null in C++ is nullptr)

Let's take a look at a sample of Predicate<T> in action:

 // Our method that we'll use as a predicate.
static public bool IsMeaningOfLife(int number)
{
   return number == 42;
}

// Elsewhere …

public void FindMeaningDemo()
{
   List<int> magicNumbers = new List<int>(new int[] { 7, 13, 1000, 42, 9 });

   // Create the predicate delegate
   System.Predicate<int> findMeaning = IsMeaningOfLife;

   // Does the meaning of life exist in my magic numbers?
   if (magicNumbers.Exists(findMeaning))
   {
      // This will be hit!
      Console.WriteLine("I've found my purpose!");
   }
}

Exists(), as you may well imagine, simply returns true if the delegate passes on any of the items in the collection.  Not too shabby, but this gets even better with...

Anonymous Delegates

Anonymous delegates are a C# (sorry C++) compiler feature that allows you to write code blocks in place of a delegate parameter.  The compiler will automatically create the proper type of delegate for you.  Simply replace the delegate parameter with:

   delegate (optional arguments) { ...code... }

If we redo the previous example using anonymous delegates we get the following:

 public void AnonymousFindMeaningDemo()
{
   List<int> magicNumbers = new List<int>(new int[] { 7, 13, 1000, 42, 9 });

   // Does the meaning of life exist in my magic numbers?
   if (magicNumbers.Exists(delegate(int number){ return number == 42; }))
   {
      // This will be hit!
      Console.WriteLine("I've found my purpose!");
   }
}

This, my friends, is where the generic collections start to really show their power.  And it gets even better.  (No, really!)  Ends up you can transparently create a delegate out of an existing variable instance:

 public void AnonymousFindMeaningDemo2(int meaning)
{
   List<int> magicNumbers = new List<int>(new int[] { 7, 13, 1000, 42, 9 });

   // Does the meaning of life exist in my magic numbers?
   if (magicNumbers.Exists(meaning.Equals))
   {
      // This will be hit (IF the method was sent one of the numbers in the collection)!
      Console.WriteLine("I've found my purpose!");
   }
}

Ok, this is somewhat contrived to fit the existing example, but you can hopefully see where this could be really useful.  (Have I mentioned that I love this stuff?  This just rocks.)

(Links to more details on anonymous delegates can be found in yesterday's post.)

Now, sadly, C++ doesn't support anonymous delegates, but I'll share a cool thing that I just haven't been able to do in C# (yet).  For your predicate argument you can take advantage of the System::Predicate constructor that takes an object and a method pointer.  Try this on:  (gcnew System::Predicate<int^>(42, &int::Equals))

Hopefully this has been useful so far, but there is more...  In my next post I'll discuss subclassing and the other System delegate types.  Stay tuned and feel free to send me your feedback...

Comments

  • Anonymous
    July 24, 2005
    Blog link of the week 29
  • Anonymous
    June 29, 2006
    Is there a way to search a Generic Collection List<of> with Multiple parameters like using more than one predicate at a time?
  • Anonymous
    May 24, 2007
    In C# 2.0, generics and generic collections are notable and very useful features to pay attention to: