Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

By a raise of hands, how many people love foreach?  I'm starting to like it quite a bit, even though I often refactor it out of a loop because I need the index.  That is probably the number one reason people actually perform the refactoring.  However, when using any non enumeration syntax, you don't get the versioning concepts that are available to enumerators, and so if the collection changes you never know.  This could be a big deal, or it might not, but the versioning is pretty neat nonetheless.

Being as I loved foreach, I figured they'd give it some love in Whidbey.  I'm back to looking at List<T>.  So if I grab an enumerator off of List<T> I can't pass it a Predicate<T>.  Have you see predicates yet?  They allow you to return a true/false as to whether or not something matches your criterion.  So enumeration in a collection can't have predicates.  Well, that kind of sucks, and is something I'd love to see added at the base level.  Basically:

GetEnumerator(new Predicate<TypeICreatedCollectionWith>(MyPredicate));

Sweet.  Now I can enumerat only those items that match my list, skipping over any that don't.  That would be perfect, but it doesn't exist yet.  So what does exist?  Well, FindAll.  This returns a List<T> based on your closed type.  Keyword is that it creates a new collection.  Not the best, but you can do it.  I'll include a full sample as the end, but a FindAll call might look like the following:

List<Organism> organisms = new List<Organism>(30);
for(int i = 0; i < 10; i++) {
    organisms.Add(new Plant());
    organisms.Add(new SeaPlant());
    organisms.Add(new LandPlant());
    organisms.Add(new Carnivore());
    organisms.Add(new Herbivore());
}

List<Organism> plants = organisms.FindAll(new Predicate<Organism>(PlantsOnlyPredicate));

The above PlantsOnlyPredicate obviously checks the type and does some work.  Notice the problem with the above?  My Predicate only returns Plants, however, List<T> is of open type Organism still.  Even worse, you can't just cast it around like you used to be able to.  So FindAll doesn't support mutation.  Is there a method that does?  Yep.  ConvertAll.  ConvertAll takes a Converter<T,V> and returns you a new List<V>.  So now the code looks something like:

List<Organism> plants = organisms.FindAll(new Predicate<Organism>(PlantsOnlyPredicate));
List<Plant> castPlants = plants.ConvertAll(new Converter<Organism, Plant>(PlantConverter));

I think it is time these two got merged eh?  What about taking a FindAll, that ALSO took a converter.  You could write a method that returned the new strongly typed collection cast up if need be.  That seems pretty cool right?  Well, it could cast an exception if your converter can't make the cast, but that really is the programmers problem.  To get back to the original problem, you now have to FindAll, ConvertAll, and then GetEnumerator to get an enumerator for only a specific sub-set of your collection (you can take out the ConvertAll if your predicate isn't doing work based on type, but instead based on some other criterion, but I remember users that wanted to enumerate over say a collection of controls, but only enumerate the buttons, say in a WinForms environment).

I have two solutions really.  The first solution just does the conversion.  It has to box the value (cast to object) in order to get around the compiler junk, but it does return your strongly typed array cast to whatever the ReturnType is.  Check it out, but notice the casting doesn't look cool because we aren't using the indirection of a converter:

private static List<ReturnType> FindAllMutate<ReturnType, ListType>
    (List<ListType> listToMutate, Predicate<ListType> predicate)
{
    List<ReturnType> mutator = new List<ReturnType>();

    foreach(ListType listItem in listToMutate) {
        if ( predicate(listItem) ) {
            mutator.Add((ReturnType) ((object) listItem));
        }
    }

    return mutator;
}

Note, you can't really use constraints here.  Why?  Well, because think of how they work and get back to me if you find a way to use them.  I couldn't find a way to do the conversion generically with a constraint since the conversion could be to a class derived from ListType, a class ListType is deriving from, or an interface ListType is implementing.

I think the Converter is pretty nice though.  It basically forces you to write a method with the ListType as a parameter and an output of the ReturnType.  C# will then worry about your casting later and as long as your Converter is written for the type you are converting, then the methods all work.  Very nice, since now the compiler has more information.  Rewritten with the converter the FindAllMutate now looks different.

private static List<ReturnType> FindAllMutate<ReturnType, ListType>
    (List<ListType> listToMutate, Predicate<ListType> predicate, Converter<ListType, ReturnType> converter)
{
    List<ReturnType> mutator = new List<ReturnType>();

    foreach(ListType listItem in listToMutate) {
        if ( predicate(listItem) ) {
            mutator.Add(converter(listItem));
        }
    }

    return mutator;
}

Well, no more sloppying casting.  And I can write whatever I would like in my converter, including custom code that knows how to create the proper type even if there isn't an easy conversion. 

That appears to accomplish my goals for this blog.  I'd love to see some more powerful collection routines added to List at the base rather than writing them.  That way I can be assured they'll be there as I'm writing code for customers.  I'm not seeing any real generics support in the Windows Forms library, so my original problem of enumerating the Controls collection for only the controls matching a particular type might not be as close as I think.  Here is some test code for predicates with a Terrarium theme, Enjoy!

using System;
using System.Collections;
using System.Collections.Generic;

public class Organism {}
public class Plant : Organism {}
public class Animal : Organism {}
public class Carnivore : Animal {}
public class Herbivore : Animal {}
public interface ISeaPlant {}
public interface ILandPlant {}
public class SeaPlant : Plant, ISeaPlant {}
public class LandPlant : Plant, ILandPlant {}

public class PredicateCategorizer {
    private static void Main(string[] args) {
        List<Organism> organisms = new List<Organism>(30);
        for(int i = 0; i < 10; i++) {
            organisms.Add(new Plant());
            organisms.Add(new SeaPlant());
            organisms.Add(new LandPlant());
            organisms.Add(new Carnivore());
            organisms.Add(new Herbivore());
        }

        List<Organism> plants = organisms.FindAll(new Predicate<Organism>(PlantsOnlyPredicate));
        Console.WriteLine(plants.Count);
        List<Organism> herbivores = organisms.FindAll(new Predicate<Organism>(HerbivoresOnlyPredicate));
        Console.WriteLine(herbivores.Count);
        List<Organism> carnivores = organisms.FindAll(new Predicate<Organism>(CarnivoresOnlyPredicate));
        Console.WriteLine(carnivores.Count);
       
        List<Plant> plantsPreCast = FindAllMutate<Plant, Organism>(organisms, new Predicate<Organism>(PlantsOnlyPredicate));
        Console.WriteLine(plantsPreCast.Count);
       
        List<ISeaPlant> plantsSeaOnly = FindAllMutate<ISeaPlant, Organism>(organisms, new Predicate<Organism>(SeaPlantsOnlyPredicate), new Converter<Organism, ISeaPlant>(SeaPlantConverter));
        Console.WriteLine(plantsSeaOnly.Count);
    }
   
    private static Plant PlantsConverter(Organism input) {
        return input as Plant;
    }

    private static ISeaPlant SeaPlantConverter(Organism input) {
        return input as ISeaPlant;
    }
   
    private static bool PlantsOnlyPredicate(Organism underlying) {
        if ( underlying is Plant ) { return true; } else { return false; }
    }

    private static bool SeaPlantsOnlyPredicate(Organism underlying) {
        if ( underlying is ISeaPlant ) { return true; } else { return false; }
    }

    private static bool HerbivoresOnlyPredicate(Organism underlying) {
        if ( underlying is Herbivore ) { return true; } else { return false; }
    }

    private static bool CarnivoresOnlyPredicate(Organism underlying) {
        if ( underlying is Carnivore ) { return true; } else { return false; }
    }
   
    private static List<ReturnType> FindAllMutate<ReturnType, ListType>
        (List<ListType> listToMutate, Predicate<ListType> predicate, Converter<ListType, ReturnType> converter)
    {
        List<ReturnType> mutator = new List<ReturnType>();

        foreach(ListType listItem in listToMutate) {
            if ( predicate(listItem) ) {
                mutator.Add(converter(listItem));
            }
        }

        return mutator;
    }

    private static List<ReturnType> FindAllMutate<ReturnType, ListType>
        (List<ListType> listToMutate, Predicate<ListType> predicate)
    {
        List<ReturnType> mutator = new List<ReturnType>();

        foreach(ListType listItem in listToMutate) {
            if ( predicate(listItem) ) {
                mutator.Add((ReturnType) ((object) listItem));
            }
        }

        return mutator;
    }
}

Published Friday, April 30, 2004 6:16 AM by Justin Rogers
Filed under:

Comments

Thursday, May 20, 2004 7:32 AM by TrackBack

# re: Internal and External Iterators

Wednesday, June 16, 2004 5:51 AM by 耳机

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

yes
Friday, October 01, 2004 7:59 AM by TrackBack

# Predicate samples for other methods on List

Friday, October 26, 2007 8:35 PM by C-Sharp

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

Interesting article.  However, your explaination is a bit fragmented.  Don't ever consider becoming a teacher.

Thursday, March 12, 2009 5:55 PM by ...

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

Sehr wertvolle Informationen! Empfehlen!

Friday, August 07, 2009 4:16 PM by Mehran DHN

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

Good point and excellent implementation.

Obviously Lack of constraints in generics is the greatest weakness.

It seems generics need a real revolution.

Wednesday, December 09, 2009 2:46 AM by sithra

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

what is Predicative genetics?

Friday, March 26, 2010 8:28 AM by Barry

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

Give please. Be honorable yourself if you wish to associate with honorable people. Help me! Looking for sites on: Share trading basics. I found only this - <a href="leadership.nlada.org/.../ShareTrading">nedbank online share trading</a>. Abroad's a overvalued influence article - you have to increase what you're charging in invitation to remember investment in the online exchange, share trading. Share trading, for the popular corporate centres, i have upto confidential stocks, shares claim australia to make about yet that they can need a better trader term in aspect. Waiting for a reply :-(, Barry from Burundi.

Friday, July 16, 2010 4:56 AM by Raam

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

Is it true that use of Predicates boosts perfromance?

Sunday, October 07, 2012 7:50 AM by icons pack

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

<a href="www.mobiledone.com/index.php I believe, that you are not right.</a>

Tuesday, October 09, 2012 6:46 AM by icons pack

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

<a href="finance.blog.techweb.com.cn/.../1618.html"> Now all is clear, many thanks for the information.</a>

Friday, March 22, 2013 1:23 PM by Elliot

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

I am really impressed together with your writing talents as smartly as

with the format on your blog. Is this a paid topic or did you customize it yourself?

Either way stay up the excellent quality

writing, it's uncommon to see a nice blog like this one these days..

Saturday, April 06, 2013 6:07 PM by Farr

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

w zębach

dratwy. Owczy Vada zewłok był duży. Zobligowany taki stanowić, iżby umieścić w sobie co niemiara siarki, saletry, węgla drzewnego zaś ze trójka

funty hufnali. Lecz poprzednie klechdy zalecały wyłączni.

Saturday, May 18, 2013 12:07 PM by Zepeda

# re: Generic predicates are pretty powerful, but the FindAll implementation doesn't show it.

Now I am going to do my breakfast, afterward having my breakfast

coming again to read further news.

Leave a Comment

(required) 
(required) 
(optional)
(required)