Using an anonymous delegate in List<T>.FindAll()

If your experience is anything like mine, you probably have been using generics like crazy since .NET v2 hit the streets (or before if you were a beta monkey). It's so much easier to manipulate object and especially strongly typed collections.

One thing I always found annoying, however, was that there wasn't an obvious way to find stuff in a List<T> object. For example, if you had a list of NavEntry objects that have Title and Url properties, there's no obvious way to pipe in some string and find all of your matches. The documentation tells you how to use a search predicate, which is not particularly useful in real life because you can't pass in parameters to it. You can of course use some private variable, but it feels "hackish" (for lack of a better word) and is less elegant.

A friend of mine, much smarter than me, suggested using an anonymous delegate. So in my example, you'd create a class like this:

public class NavEntryList : List<NavEntry>
{
    public List<NavEntry> GetItemsContaining(string text)
    {
        return this.FindAll(delegate(NavEntry nav) { return nav.Title.Contains(text); });
    }
}

I like the way that rolls much better. You can substitute the Title property for whatever it is your object has, and even create one for each property, if you'd like. You can probably refactor this even more. I've seen a third-party class library that does all kinds of neat stuff like this, though the name escapes me at the moment.
 

38 Comments

  • Maybe someone reading this can tell me what this line is in VB

    return this.FindAll(delegate(NavEntry nav) { return nav.Title.Contains(text); });

  • but you can pass in parameters to a predicate and create much more robust search patterns with them via encapsulation in the predicate:

    private class MyPredicate
    {
    private string foo;
    private bool bar = false;

    public MyPredicate(string foo) { this.foo = foo; }

    public MyPredicate(string foo, bool bar)
    {
    this.foo = foo;
    this.bar = bar;
    }
    public bool find(string s)
    {
    if (bar)
    return this.foo.Contains(s);
    else
    return this.foo.Equals(s);
    }
    }

    usage (where this is List):

    MyPredicate p = new MyPredicate("test");
    this.FindAll(p.Find);

    -or-
    MyPredicate p1 = new MyPredicate("test", true);
    this.FindAll(p1.Find);

    that's just a simple example. HTH

  • Avoid using anonymous methods: they are hard to understand. Previous example is much more easy to understand.

    Following article explains it all...

    http://dotnet.sys-con.com/read/319753.htm

  • Hard to understand? I'll admit that delegates in general are kind of a weird and abstract concept to grasp at first, but how is my example not easy to follow?

    Trey's example is pretty good if you need to do something more complex. Mine was intended to solve the problem I encounter most of the time quickly and in a line of code.

  • I can't wait for C# 3.0 when I get to write:

    return this.FindAll(nav => nav.Title.Contains(text));

  • Jeff I was just about to post but Duncan beat me to it. As he said this:

    return this.FindAll(delegate(NavEntry nav) { return nav.Title.Contains(text); });

    Will be able to be written as this in C# 3:

    return this.FindAll(nav => nav.Title.Contains(text));

    Also note that VS Orcas will actually let you write this code using C#3 targetted against .NET 2. In other words you will be able to run this code on .NET 2 without needing .NET 3 or 3.5 installed on the client machine because it is all some by the compiler and does not require runtime or library support :-) (thanks for ScottGu for clarifying this).




  • That's very sweet. I was all over the betas of v2, seeing as how I was only writing my book at the time, but with a "real" job this time around I just haven't taken much time to look at v3 stuff outside of what Scott has been posting.

  • I'm amazed that passing in an anonymous predicate isn't totally obvious. It's the entire reason all those finders were added to the collection hierarchy in the first place.

    This is totally standard practice in most decent languages. If this looks cool or tricky to you, you need to put down C# and learn some better languages.

    Ruby, Scheme, Python, or Smalltalk would be a good start. C# is a rather impoverished language and you don't know what you're missing if you haven't tried a couple other good languages.

    For one thing, you shouldn't have to subclass a generic list add a method like GetItemsContaining to a list, the whole point of the generic finders and generic lists is that you can use the generic list "as is" without needing to subclass it. If you subclass it, it's no longer a generic list and you've gone back to the old method of type safe list subclasses, YUK. You should do this instead...

    IList SomeMethod(string someText){
    IList entries = //fetch from somewhere
    return entries.FindAll(delegate(NavEntry each){
    return each.Title.Contains(someText);
    });
    }



    The point of the anonymous method is that it allows the client to search for anything he wants without you needing to write special search methods like GetItemsContaining. If you're going to use generics, use them right, don't subclass them. You shouldn't have to subclass a generic list, it defeats the purpose.

    Other languages do this better. For example, in Smalltalk this example would look like this...


    someMethod: someText
    entries := //fetch from somewhere
    ^entries select: [:each | each title includesSubString: someText ]



    In Ruby it'd look like this...


    def someMethod()
    entries = //fetch from somewhere
    entries.select {|each| each.title.include? text }
    end



    In Scheme, it'd be something like...


    (define (some-method some-text)
    (let ((entries '("fetch" "from" "somewhere")))
    (filter (lambda (each)
    (substring? each "text"))
    entries)))



    Seriously, using anonymous methods and higher order functions is par for the course in any decent language. Don't let just one language shape your thoughts. Anonymous predicates are basic stuff, not some fancy pattern only smart people use.

  • I was with you right up until you started dissing the language. How is that constructive or even relevant? Shall I tell my employer (and most of the employers in my area), "Hey, forget this C# stuff. Use Smalltalk instead!"

  • I'm not dissing it, I use it daily as well, both VB and C#. I'm simply saying it's not the greatest language to "learn" things in. One should learn Smalltalk because it'll make you a better object oriented programmer in any OO language. One should learn Scheme/Lisp because make you a better functional programmer in any functional language.

    These languages are what's called "pure", whereas C# is a mixed hybrid that makes learning new things difficult in comparison.

    Everything you learn in Smalltalk/Lisp will directly transfer to "any" language you work in because all those other languages in one way or another are derivative of Smalltalk/Lisp. They're the Latin of programming languages. Once you know them, everything else looks easy, and less capable.

  • That's not an accurate assessment of how the real world works. Only half of the people I work with majored in anything computer related in college. The rest of us did a number of other things. We learned what we had to do to make us marketable.

    Does not knowing the finer points of language design or design patterns make us less useful developers? I'd say not. I think we write some damn good software, and frankly we deliver useful stuff quickly.

  • Um, I am one of the rest of you. I'm self taught. I'm not quoting stuff I learned in college, I didn't even go to college. I work in the real world just like everyone else, and what works in the real world is delivering results.

    C# might make you marketable, but Smalltalk/Lisp will make you a better C# programmer, and in the real world, that matters, and ultimately makes you even more marketable. This isn't abstract computer science stuff I'm talking about here, it's basic down in the dirt programming. Passing around anonymous delegates(aka functions) is something even JavaScript does constantly, and that's about as real world as you can get.

    Learning isn't a zero sum game, you aren't going to learn less about C# by also learning other languages. Quite the contrary, the other languages will help you learn and apply skills faster, and will apply directly to C#. Programming is in the mind, not in the syntax, and those other languages enable your mind to flourish faster than C# can.

  • I don't think you're seeing my point. When am I going to do this? In my spare time? That time is reserved for being social, hot tubbing, or traveling!

  • No, I see your point, I just don't agree with it. If you can't find a little time now and then, outside of work to sharpen the knife, you're just making your work that much more difficult and time consuming.

    Once you sharpen the knife a bit, the work itself becomes so much easier, that it becomes easy to fit this stuff into work hours. You don't have to be able to write code in these language, to learn to read them. Learning to read them alone, is enough to benefit from the ideas you'll get from them.

    For example, subclassing a generic list to make a type safe list shows such a gross misunderstanding of generics that I can tell right away you aren't actually benefiting from them as much as you should be. Proper use of generics could actually reduce your workload by tenfold or more, if only you sacrificed some spare time to study them better.

    When you declare an instance of a generic list

    IList list = new List();

    Conceptually what is happening is the compiler generates a CustomerList for you, on the fly. Generic lists are supposed to save you from having to manually build type safe lists.

    But generics are much more powerful than this, they can be seen as code generators that greatly reduce your workload by having generic templates that you subclass and parameterize with the subclass itself in its own declaration.

    class Customer : BizObject {}

    Could instantly give you access to an api like Rails but completely type safe with no casting...

    Customer customer = Customer.Find(300);

    IList customers = Customer.FindAll(delegate(Customer each){
    return each.Name.StartsWith("a");
    });

    Money totalOrderAmount = Customer.FindAll().Reduce(Money.Dollars(0), delegate(Money sum, Customer each){
    return sum + each.OrderTotal;
    });

    Because you can write methods like Find, FindAll, Reduce, Map, Collect, Reject, etc. totally generically without a specific type in mind knowing it'll be passed up as a parameter later.

    You could take any code you write that appears at all repetitive, and make a generic template that completely automates that for you in the future. The point is, the effort you must put into learning this stuff, even if it's in your off time, will pay you back a hundred fold by reducing your work load and actually giving you more free time in the end. So the truth is, you don't have time not to do it.

  • I recently developed some code where I created TList objects. all they did was inherit from List. I did that to make the code more readable for coworkers that don't know generics (yet). They can now say:

    ItemList items = ItemManager.GetAll();

    But they can also still do this:

    ItemList items = ItemManager.GetAll().Find(
    delegate(Item item) {
    return item.Name.StartsWith("a");
    }
    );

    How is that not good? I still have the power of the generic list, but my API is more expressive (Item, ItemList and ItemManager). Also, there might be methods you want on the list that are complicated enough that you don't want to repeat them where you use it (in the anonymous delegate), so now there's a place to store it.

    I'm interested in your feedback because your strong statements made me doubt my own methods. I'm trying to sharpen the knives here, so to speak ;-)

  • Work is not difficult or time consuming. That's why I made the comment that we're doing quite well. Learning another language takes a hell of a lot more than "a little time now and then."

  • Ramon's comments have certainly drawn my interest into looking at the core concepts in languages like Smalltalk. See how I said "looking at", not "learning a whole new language". It's about grasping concepts, being able to apply them and to see parallels in different languages, that in the end aids your developer toolkit.

    Honestly Jeff, you seem to over-argue and "yay-nay" until the cows come home instead of simply saying saying "Wow, how interesting I'll have a look at that."

    In the end that kind of attitude would fare you much better.

    And yes, I don't give a hoot you won't give a hoot about my opinion either. ;-)

  • That's because I'm more business minded than most code monkeys. Even if something is "interesting" I attach some value to it. I can't possibly find the time to follow up on every thing that's interesting to me.

  • "How is that not good? I still have the power of the generic list, but my API is more expressive (Item, ItemList and ItemManager). "

    @Mike, because you now have to maintain that cruft. Subclassing a generic class to produce a type safe version is exactly what generics are meant to prevent. It's useless code that adds nothing of value.

    "Also, there might be methods you want on the list that are complicated enough that you don't want to repeat them where you use it (in the anonymous delegate), so now there's a place to store it."

    @Mike, a common mistake I've seen people make. You should never put methods like this on a list. A list is a generic building block, specific code like this should go into specific predicates that can be passed to the lists generic methods. Put them as nested classes in your business class, then you can write code like this...

    IList deservesRaises = someList.FindAll(new Employee.EvaluatePerformancePredicate());

    This puts the complex predicate, which contains business rules, where it belongs, in the biz object, and keeps the list, totally generic, a simple building block.

    You might do something similar for IComparers and sorting...

    IList getsParkingSpotOrder = someList.FindAll().Sort(new Employee.ByPerformanceASC());

    "I'm interested in your feedback because your strong statements made me doubt my own methods. I'm trying to sharpen the knives here, so to speak ;-)"

    Good, more people should, it takes very little effort and the payback is tremendous.

    @Jeff, I can't possibly believe huge increases in productivity aren't of interest to you.

    "Learning another language takes a hell of a lot more than a little time now and then."

    Actually, it doesn't, since most of them are similar, and you only need learn to read to benefit, not write. Learning to write another language can take more than a little time.

  • Unfortunately MS decided to use Java as their starting point for C#. Ah well. That's marketing for ya.

    Ah, Lisp. You can build some great data retrieval systems using it. Sometimes I wish SQL didn't take off just so that Lisp would've become more popular.

  • One of the challenges of self-learning a new technology is to get the overall picture.

    I have found a good number of articles on generics but they focus on one aspect and do not provide the overall picture.

    Ramon's comments were really useful for me to see there's a larger picture for generics.

    Is there a book, blog and/or article that you recommend?

  • Is very nice, high five!

  • Ramon, you've convinced me to take a look at Smalltalk, sheerly to better grok functional programming. The writing is on the wall with LINQ. I am preparing for the next paradigm in .NET programming. There is no since resisting the best practices that Anders Heljsberg and the C# team are preaching dictate that the functional paradigm is faster, more error free, and has a smaller footprint than imperative programming. Lambdas are going to make writing the monkey code involved in nested iterations and branching a thing of the past. Honestly, I can't wait till my org moves on to Orcas for this reason alone...

  • ok, I'm a little late, it's November already..

    IList customers = Customer.FindAll(delegate(Customer each){

    return each.Name.StartsWith("a");

    });

    works great for me, except that you first have to know that you WANT the Name from WHICH type. I don't know WHAT I want from WHICH type, so I have to find a way of saying:

    IList t = T.FindAll(delegate(T each){

    return each.(GetPropertyName("sPropertyName")).StartsWith("a");

    });

    Does anyone know if this is possible?

  • ehm, I know that the example code won't ever work.. It is meant "as a matter of speaking".. I guess Im looking for a dynamic Predicate method?

  • a dynamic "Predicate" method Like:

    public bool StartsWith(string sPropertyName, string sSearchString,T tInstance)
    {
    PropertyInfo[] properties = typeof(T).GetProperties();
    bool bReturn = false;
    //Find the right property and return result
    foreach (PropertyInfo property in properties)
    {
    if (property.Name == sPropertyName)
    {
    bReturn = ((string)property.GetValue(tInstance, null)).ToLower().StartsWith(sSearchString.ToLower());
    }
    }
    return bReturn;
    }

    and call it like:
    SomeList = SomeList.FindAll(delegate(C_Column each)
    {
    return StartsWith("APropertyName", "ASearchString", each);
    });

    but as you see, I have to loop through all properties for every value in my list... So, is there no better way?

  • Real late to this but I'm shocked Jeff had a problem with Ramon's comments. Some of the quotes from Jeff include:

    "I'll admit that delegates in general are kind of a weird and abstract concept to grasp at first, but how is my example not easy to follow?"

    "Does not knowing the finer points of language design or design patterns make us less useful developers? I'd say not. I think we write some damn good software, and frankly we deliver useful stuff quickly."

    "That's because I'm more business minded than most code monkeys."

    You admit this about abstract concepts, then try to say not knowing the finer points isn't so bad, then go on to say being business minded is most important.

    The key point you failed to see from Ramon was that learning the basics DOES make you a better developer because it gives you a great foundation for picking up abstract concepts faster and in turn results in you being able to apply those concepts in the office faster.

    New features in C#? A good foundation will probably give you a quicker path to learning and applying those features. Technical problem in your application slowing you down? A good foundation will allow you resolve those issues faster.

    It's just ironic that you admit the concept is abstract but valuable yet are upset when someone points out a way to make such abstract concepts easier to pick up.

  • @Rogier: Here is one possible way to do what you want...

    using System.Reflection;
    using System.ComponentModel;
    ......
    class Item{
    string name = string.Empty;
    public Item(string name){ name = name; }
    public string Name{ get{ return name; } }
    }
    ......
    Console.WriteLine("Item.Name = " + items.Find(delegate(Item _i)
    {

    return ((new TypeConverter().ConvertTo((((PropertyInfo)Array.Find(typeof(Item).GetProperties(), delegate(PropertyInfo _p)
    {
    return _p.Name == prop;
    })).GetValue(_i, null)), f3.GetType()))).Equals(f3);

    }).Name
    );


    @Generally speaking:
    I'd like to be able to do somthing similar to this.
    IList items ...
    IList x = items.FindAll(Item.Name == "some value or parameter"); where == could be substitutet for = etc or Item.Name.Equal(value)

    One could do something similar to:
    Items items = new Items();
    items.Add(someValue);
    IList found = items.FindAll("Item.Name == some value");
    And the parse and get property info etc

  • oups forgot some variables.

    string f3 = "value to search for";
    string prop = "name of property"; // e.g. Name for Item.Name

  • Here is another example
    List young = people.FindAll(delegate(Person p) { return p.age < 25; });

  • One could do something similar to:

    Items items = new Items();

    items.Add(someValue);

    IList found = items.FindAll("Item.Name == some value");

    And the parse and get property info etc

  • The problem is that it can get messy...

    private List<KeyValuePair<Guid, List<KeyValuePair>>> projCats = new List<KeyValuePair<Guid, List<KeyValuePair>>> ( );

    this.ddlCategory.DataSource = StaticData.Instance.ProjCats.Find ( delegate ( KeyValuePair<Guid, List<KeyValuePair>> item ) { return item.Key.ToString ( ) == this.ddlCategoryProject.SelectedValue; } ).Value;


  • Why don't you use PropertyInfo?

    PropertyInfo property = typeof(T).GetProperty(sPropertyName);


    >>Rogier said:
    >>but as you see, I have to loop through all properties for every value in my list... So, is there no better way?

  • You're surprised I have no interest in learning a dead language? Really?

  • wow Jeff you're very defensive mate. Can't you have a conversation without being a smart ass?

  • How is that being a smart ass? You called me out, I responded. Shall I be passive and withhold my opinion because you don't like it?

  • Hey,

    Could I ask a simple question about FindAll?

    If I have a List of Objects which includes "Name" and "Status" properties. I want to quickly find all of the entries that match a certain criteria so assume I can use the FindAll method for that?

    However what I ALSO want to do is then update the "Status" property on each of those records found. If I modify the FindAll result set is it actually modifying a reference of the original List Object or is it a copy of each Object?

    Currently I use a ForEach loop to do this but I'm hoping FindAll can replace that if I can just understand in my own mind if it works and it would be more performant not to mention simpify my code.

    Any example greatly appreciated!

    Regards

    Julian

  • Trey you need to capitalize your "find" method my friend.

Comments have been disabled for this content.