Delegate-Based APIs



Generics and Anonymous Methods/Delegates make up a powerful pair that can be used to create elegant query APIs. Here are some that we just added to the .NET Framework’s Collection libraries. I used List<T> to illustrate the APIs but most of those were also added to System.Array.



API Design


Delegates


namespace System {


   public delegate void Action<T>(T obj);


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


   public delegate U Converter<T,U>(T from);


   public delegate int Comparison<T>(T x, T y);


}


List<T>


public class List<T> : … {


   public int FindIndex(Predicate<T> match);


   public int FindIndex(int index, Predicate<T> match);


   public int FindIndex(int index, int count, Predicate<T> match);



   public int FindLastIndex(Predicate<T> match);


   public int FindLastIndex(int index, Predicate<T> match);


   public int FindLastIndex(int index, int count, Predicate<T> match);


 


   public List<T> FindAll(Predicate<T> match);


   public T Find(Predicate<T> match);


   public T FindLast(Predicate match);



   public bool Exists(Predicate<T> match);


   public bool TrueForAll(Predicate<T> match); 


   public int RemoveAll(Predicate<T> match);



   public void ForEach(Action<T> action);


   public void Sort(Comparison<T> comparison);


   public List<U> ConvertAll<U>(Converter<T,U> converter);


}


Finding Even Integers in List<T>


List<int> integers = new List<int>();


For(int i=1; i<=10; i++) integers.Add(i);


List<int> even = integers.FindAll(delegate(int i){


   return i%2==0;


});


Finding Complex Type in List<T>


public class Order {


   public Order(int number, string item) { … }


   public int Number { get { return number; } }


   public string Item { get { return item; } }


   …


}


List<Order> orders = new List<Order>();


int orderNumber = 10;


Order order = orders.Find(delegate(Order o){


   return o.Number==orderNumber;


});


Computing Sum of Integers in List<T>


List<int> integers = new List<int>();


for(int i=1; i<=10; i++) integers.Add(i);


int sum;


integers.ForEach(delegate(int i){ sum+=i; });


Sort Orders in List<T>


List<Order> orders = new List<Order>();


orders.Add(new Order(10,”Milk”));


orders.Add(new Order(5,”Cheese”));


 orders.Sort(delegate(Order x, Order y){


   return Comparer<int>.Default.Compare(x.Number,y.Number);


});


Convert Orders to Order Numbers


List<Order> orders = new List<Order>();


orders.Add(new Order(10,”Milk”));


orders.Add(new Order(5,”Cheese”));


List<int> numbers = orders.ConvertAll(delegate(Order x){


   return o.Number;


});

Comments (39)

  1. Herb says:

    Finally! You can almost smell the STL from here. The use of anonymous delegates is further icing on the cake at this point.

  2. Sweet. This is awesome. I agree with Herb. That’s one of the things I missed most about C++ when switching to C#… No STL.

    Not only is the STL coming back, but it’s coming back with added bonuses.

    Keep up the awesome work.

  3. mike says:

    very, very nice to have.

    but…

    > public List<U> ConvertAll<U>(Converter<T,U> converter);

    can’t you just call it map(), like everyone else does? 😉

  4. damien morton says:

    Many of these methods belong in Enumerator<T> and/or Enumerable<T>. In particular, any of the methods that dont involve an index.

    Enumerator<T>/Enumerable<T> should have an implicit conversion operator to List<T>.

  5. Taylor Wood says:

    This is awesome. I have already thought of 10 instances I can use this in, so hurry the f%@* up!

  6. Why did you decide to make these member functions and not static helper functions? Are they part of the IList<T> interface? Will we have to implement this functionality for every list-like object we’d like to supply it for?

  7. damien morton says:

    also on the subject of Enumerable<T>

    I want to see operator+(Enumerable<T>, Enumerable<T>) which will concatenate two sequences

  8. damien morton says:

    IEnumerable<T> operator+(IEnumerable<T> a, IEnumerable<T> b)

    {

    foreach (T t in a)

    yield return t;

    foreach (T t in b)

    yield return t;

    }

  9. gb says:

    wow, C# is becoming ruby, I’m sure if the .net guys quoted it they would get lot of feedback 😉

  10. Sean says:

    Its just a pity the anonymous delegate syntax sucks.

  11. Krzysztof Cwalina describes some new delegate-based APIs added to the .NET collection libraries. A couple of points came to mind on reading this. One, would it be better if the methods returning List instead returned IEnumerator, using an iterator for…

  12. Ryan Heath says:

    Wow, this is cool.

    Much nicer and cleaner than the use of those (almost silly) functor objects icw STL in c++ 😉

    // Ryan

  13. -- says:

    brain. hurts.

  14. Michael says:

    Ugh – having all these algorithms implemented as member functions is really gross. One of the coolest things about STL is how algorithms have been separated from container definitions.

    With the STL method, I can write a new container, and have the existing algorithms function with it. With the method shown here, I have to implement all the algorithms for my container every time I make a new container? That’s not cool.

    It still has a way to go before reaching the flexibility and elegance of STL…

  15. Ryan Lamansky (Kardax) says:

    Michael:

    Anonymous methods are just shorthand for delegates.

    Using delegates, you could separate the container from the algorithms with little difficulty. You could even mix delegates with anonymous methods.

  16. Krzysztof: What is the difference between:

    U Converter<T,U>(T t)

    and

    B Function<A,B>(A a)

    Is the latter included in the FX?

  17. Thong Nguyen says:

    About time!

    Are you going to be supporting some of these higher order functions in the string class?

    Things like Left(Predicate), Right(Predicate), LeftFromRight(Predicate), RightFromLeft(Predicate), Substring(Predicate, Predicate), TrimLeft(Predicate), TrimRight(Predicate), Convert(Converter), CountChars(Predicate) etc etc etc etc.

    My C# class library supports union and intersection of predicates via operator overloading (C# doesn’t allow overloading operators on delegate classes but IL does). Are you going to support this?

  18. lukester says:

    Its better to follow the lesson of STL and externalize the algos…

    A few comments in here allude to it: define the algorithms as delegates that operate on enumerators and take delegates as arguments, then the container need not worry about adding methods to support these operations.

    public delegate T for_each<T>( IEnumerator begin, IEnumerator end, T delegate )

    It seems a right and proper STL implementation could now be created for C#.

  19. James Bellinger says:

    Hmm. Any plans to add a Slice method and perhaps a Merge method for arrays? That would be beautiful (the former more important than the latter). In fact, something like a Python-style slice syntax would be beautiful, and maybe + to merge arrays… sorry, dreaming.. 🙂

  20. Zohar says:

    Great Work.

    This will make the platform a much better place.

    Ho about applying this to more areas :

    File.AsText("MyFile.txt").EachLine(delegate( line string){

    // do something with line

    });

    same for db’s etc.

    this also means you can wrap the usage internaly with a using block, and save the users the need to remember to manage the underlying resource….

  21. Peter Golde says:

    The names for these methods seem a little inconsistent to me. The word "All" seems to me lots of different things.

    In "TrueForAll" and "ConvertAll", it appears to mean all the items in the collection.

    In "FindAll" and "RemoveAll", it appears to only mean the items that satisfy the predicate.

    I would suggest the following renamings:

    Find -> FindFirst (for symmetry with FindLast)

    FindIndex -> FindFirstIndex

    RemoveAll -> RemoveWhenTrue

    FindAll -> FindWhenTrue

  22. Joe Walnes says:

    Martin Fowler (obligitary Fowlbot namedrop) recently blogged about the power of closures in languages that support them. It’s worth remembering that C# 2.0 has true closure support in the form of anonymous delegates. This includes reading and modifying variables outside…

  23. zdzislaw (jesse) Cwalina, MI says:

    Krzysztof you are coming from noble family in poland from region of kurpie and have a "herb" godziemba.png

    Zjesse123@yahoo.com

  24. Yes, my dad is from Kurpie. I have heard the coat of arms was called "Pniejnia". I even found a nice picture of it on the net with some legend describing the origin. See http://tkwapinski.webpark.pl/herby_p.html#a

    … and based on your name I gather we might be a family? 🙂