Click or type ~ to show Console
Type Ctrl-C to close console. Type 'wat?' to find out how this console was created.
Welcome to the Iloggable Interactive Console. You can navigate posts either by file hierarchy with cd [path] or via paged posts lists using the posts [page] command. You can navigate to a new post with the go [path|#id].

ILoggable

A place to keep my thoughts on programming

December 14, 2007 .net , ,

Action & Func: Never write another delegate

With lambda expressions in C#, the Func
generic delegate and it's variations have been getting a lot of attention. So naturally, you might think that the lambda syntax is just a shortcut for creating anonymous delegates, whether they return values or not.

First let's look at the evolution of delegates from 1.1 to now. Delegates, simply are the method equivalent of function pointers. They let you pass a method call as an argument for later execution. The cool thing (and a garbage collection pitfall) is that a delegate creates a lexical closure, i.e. the delegate carries with it the object that the method gets called on. For garbage collection this means that a delegate prevents an object from being collection. That's why it's important to unsubscribe from those events you subscribed to.

But I digress. Let's define a delegate that returns an Integer and a method that matches that delegate:

delegate int IntProducerDelegate();

public int x = 0;
public int IntProducer()
{
  return x++;
}

With the original .NET 1.0 syntax we'd create the delegate like this:

IntProducerDelegate p1 = new IntProducerDelegate(IntProducer);
Now we can call p1() and get an integer back, and since it's closure, each time we call p1() the originating objects x increases as does our return value.

Then, in .Net 2.0 we got anonymous delegates.

IntProducerDelegate p2 = delegate { return IntProducer(); };

// or with IntProducer's action inlined...
IntProducerDelegate p3 = delegate { return x++; };

This got rid of the need to create a method just to pass along a closure that manipulated our object at a later time. The other thing that anonymous delegates re-inforce is that delegates just care about signature. IntProducerDelegate can get assigned any delegate that takes no argument and returns an int. That sounds like a perfect scenario for generics and in .NET 3.5, we got just that, a set of generic delegates called Func. Using Func, we quickly get to our lambda expression replacing the original delegate syntax like this:

// create a new Func delegate just like the IntProducerDelegate
IntProducerDelegate p3 = new Func<int>(IntProducer);

// which means that we don't need IntProducerDelegate at all anymore
Func<int> p4 = delegate { return x++; };

// and the anonymous delegate can also be shorthanded with a lambda expression
Func<int> p5 = () => { return x++; };
// which says, given that we take no argument "()", execute and return the following "return x++;"

However, before there ever was Func, .Net 2.0 introduced the generic delegate Action, which is a natural counterpart to Func, encapsulating a method that does not return anything. Following through the example of the producer, we'll create a consumer like:

delegate void IntConsumerDelegate(int i);

public void IntConsumer(int i)
{
  Console.WriteLine("The number is {0}", i);
}

Now following the same evolution of syntax we get this:

IntConsumerDelegate c1 = new IntConsumerDelegate(IntConsumer);

IntConsumerDelegate c2 = new Action<int>(IntConsumer);

Action<int> c3 = delegate(int i) { Console.WriteLine("The number is {0}", i); };

Action<int> c4 = (i) => { Console.WriteLine("The number is {0}", i); };

So lambda syntax can be used to create either a Func or an Action. And that also means that we never have to explicitly need to create another delegate, being able to use a variation of these two generic delegates as our arsenal for storing lambda expressions of all kinds.

11 to “Action & Func: Never write another delegate”

Trackbacks/Pingbacks

  1. [...] 5, 2011 A while back I wrote that you really never have to write another delegate again, since any delegate can easily be expressed as an Action or Func. After all what's preferable? [...]

  2. [...] unlike my previous statement, there are still scenarios where you have to write a delegate Author: arne on Category: [...]

  1. ether says...

    Ok, so “never write another delegate is hyperbolic”. But in the end, Func is just a delegate, so you could define another n versions of Func with n parameters and keep those definitions in some core lib.

  2. Philip says...

    Clear & very well explained, keep up excellent work.

  3. Philip says...

    Clear & very well explained, keep up the excellent work. Thanks a lot.

  4. cburgdorf says...

    best explanation I read on the net so far. Keep up the great work!

  5. mkazanova says...

    Harinatha Narisetty, I always try this logic to send parameters to functions.

    If you need more than 4 parameters consider packing them in a class or struct.

  6. Abhang Rane says...

    So this is what I think. In general it is a good coding practice to limit the number of parameters passed to or less than 4. And hence Action and Func have limited to T1,T2,T3 and T4. As said above, package further parameters in structs/classes and pass along.

  7. Func/Action vs. Delegate says...

    [...] 5, 2011 A while back I wrote that you really never have to write another delegate again, since any delegate can easily be expressed as an Action or Func. After all what's preferable? [...]

  8. BlueRaja says...

    “That sounds like a perfect scenario for a delegate”
    - Is this supposed to read “That sounds like a perfect scenario for generics?”

  9. arne says...

    @blueraja

    Yes, it should be have been.. Corrected

  10. Ok, so there are still delegates you have to define says...

    [...] unlike my previous statement, there are still scenarios where you have to write a delegate Author: arne on Category: [...]

  11. Thai says...

     

    // create a new Func delegate just like the IntProducerDelegate  
    IntProducerDelegate p3 = new Func<int>(IntProducer);
    ==> this one has an complie error ? why ??

Leave a comment