Anonymous Methods, Generics and Enumerators

Photo by boskizzi (Used under Creative Commons) I’ve been playing around with Anonymous Methods in Delphi 2009 a little bit lately, and I thought one of my experiments might be worth sharing.

I decided I would try to extend TList<T> so that when you enumerate over it in a for..in loop, not every item would be returned. Specifically, only items that passed a filter criteria would be returned, and I would use an anonymous method to specify the filter criteria.

This actually turned out to be kind of fun, as I got to play with Enumerators, Generics and Anonymous Methods all in one fairly short piece of code.

So, first I looked at TList<T>. I was hoping there was some way I could specify that it should use a different Enumerator than the default, but unfortunately I could not find a way. So I descended from TList<T> to create a TFilteredList<T> to:

  • add a couple of methods, ClearFilter and SetFilter, which I’ll come back to later
  • define TFilteredEnumerator<T>
  • reintroduce the GetEnumerator method to create an instance of TFilteredEnumerator<T> instead of TList<T>’s standard TEnumerator<T>

I also declared an anonymous method type called TFilterFunction<T> which takes a parameter of type T and returns true or false depending on whether the parameter passed or failed the filter criteria respectively.

Here’s the definition:

Most of the methods on TFilteredEnumerator<T> are not terribly interesting. The constructor takes a reference to an instance of our TFilterFunction<T> anonymous method, which it stores in the FFilterFunction field. This constructor gets called from the aforementioned GetEnumerator method of TFilteredList<T>.

Most of the hard work is done by the MoveNext method, which looks like this:

It is invoked when the Enumerator wants to move to the next item in the List, returning True if it does this successfully, otherwise returning False.  In this case, it:

  • first checks to see if we’re already at the end of the List, in which case it bails out returning False, we’re at the end of the list.
  • otherwise, it keeps incrementing the position until either we’re passed the last item or we hit an item that passes our filter criteria.

ShouldIncludeItem invokes our FIlterMethod if one is defined, passing in the current item in the list. It looks like:


The following code creates a TList<TPerson> and loads it up, then enumerates over each TPerson in the list that is older than 18. You can see the call to SetFilter where we specify the anonymous method that will do the actual filtering. It then clears the filter and enumerates over all the TPerson objects.

This produces the following output:

————— Filtered Person List —————

Name = Fred Adult, Age = 37

Name = Mary Adult, Age = 18

————— Unfiltered Person List ————-

Name = Fred Adult, Age = 37

Name = Julie Child, Age = 15

Name = Mary Adult, Age = 18

Name = James Child, Age = 12

The generics come into play so that I can use the same list for something other than TPerson objects, in the example below, Integers:

Now, I’m not sure that I really want to write my code like this. I like the fact that there’s a nice separation between the code that decides which items to act on, and the code that actually acts on them, rather than having them all jumbled together inside the for..in loop.

Ironically, that’s also the bit that I don’t like, as I can see that the filter code could possibly be overlooked by someone not familiar with anonymous methods, and think that we were acting on all the items in the list.

Yet again, I want to have my cake and eat it to.

However, as I said at the start this was an experiment to teach me a bit more about anonymous methods, so from that point of view it worked, and hopefully you got something out of it as well.

You can download the code from my delphi-experiments repository on github.

4 Comments

  • This is really cool to see in Delphi as opposed to C++.

    It illustrates again how much more “economical” the Object Pascal language is vs. C++.

    The example also shows how blurred the line is getting between dealing with data that resides in a database vs. data that resides elsewhere.

    Thanks!
    -David

  • Another great article, keep them coming!

    Would it be possible to add an enumerator to TList using a class helper?

  • Hey Malcolm,

    Have you considered adding a GetEnumerator to the enumerator class? It would just return self. Then you could construct it in the for…in statement and pass the anonymous function to it’s constructor. Then you could do this:

    FilterFunction := function(Item : TPerson): boolean
    begin
    Result := Item.Age >= 18;
    end;

    for P in TFilteredEnumerator.Create(PersonList, FilterFunction) do
    begin
    writeln(P.ToString);
    end;

    I have a more complete example at http://www.winestain.com.au/?e=16

    N@

  • Hi N@

    I’m going to use that somewhere else actually, thanks. Nice one.

    Cheers
    Malcolm

You must be logged in to post a comment.