Wednesday, 5 December 2012

Action<> in C#


A simple way of thinking about Action<>

Most of us are pretty familiar with finding sections of repeated code, pulling that code out into a method and making that method take parameters to represent the differences.
Here is a small example, which should look pretty familiar:
01public void SteamGreenBeans()
02{
03    var greenBeans = new GreenBeans();
04    Clean(greenBeans);
05    Steam(greenBeans, Minutes.Is(10));
06    Serve(greenBeans);
07}
08 
09public void SteamCorn()
10{
11    var corn = new Corn();
12    Clean(corn);
13    Steam(corn, Minutes.Is(15));
14    Serve(corn);
15}
16 
17public void SteamSpinach()
18{
19    var spinach = new Spinach();
20    Clean(spinach);
21    SteamVegetable(spinach, Minutes.Is(8));
22    Serve(spinach);
23}
Each one of these methods pretty much does the same thing.  The only difference here is the type of vegetable and the time to steam it.
It is a simple and common refactor to refactor that code to:
01public void SteamGreenBeans()
02{
03   SteamVegetable(new GreenBeans(), 10);
04}
05 
06public void SteamCorn()
07{
08    SteamVegetable(new Corn(), 15);
09}
10 
11public void SteamSpinach()
12{
13    SteamVegetable(new Spinach(), 8);
14}
15 
16public void SteamVegetable(Vegetable vegetable, int timeInMinutes)
17{
18    Clean(vegetable);
19    Steam(vegetable, Minutes.Is(timeInMinutes));
20    Serve(vegetable);
21}
Much better, now we aren’t repeating the “actions” in 3 different methods.
Now let’s imagine we want to do something more than steam.  We need to be able to fry or bake the vegetables.  How can we do that?
Probably we will have to add some new methods for doing that.  So we will end up with something like this:
01public void SteamVegetable(Vegetable vegetable, int timeInMinutes)
02{
03    Clean(vegetable);
04    Steam(vegetable, Minutes.Is(timeInMinutes));
05    Serve(vegetable);
06}
07 
08public void FryVegetable(Vegetable vegetable, int timeInMinutes)
09{
10    Clean(vegetable);
11    Fry(vegetable, Minutes.Is(timeInMinutes));
12    Serve(vegetable);
13}
14 
15public void BakeVegetable(Vegetable vegetable, int timeInMinutes)
16{
17   Clean(vegetable);
18   Bake(vegetable, Minutes.Is(timeInMinutes));
19   Serve(vegetable);
20}
Hmm, lots of duplication again.  No problem.  Lets just do what we did to the first set of methods and make a CookVegetable method.  Since we always clean, then cook, then serve, we should be able to just pass in the method of cooking we will use.
Oh wait, how do we do that?  We can’t just extract out Bake or Fry or Steam, because the Bake, Fry and Steam methods are logic and not data.
Unless… unless we can make them data.  Can we do that?
We sure can, check this out:
01public void SteamVegetable(Vegetable vegetable, int timeInMinutes)
02{
03    CookVegetable(vegetable, Steam, timeInMinutes);
04}
05 
06public void FryVegetable(Vegetable vegetable, int timeInMinutes)
07{
08    CookVegetable(vegetable, Fry, timeInMinutes);
09}
10 
11public void BakeVegetable(Vegetable vegetable, int timeInMinutes)
12{
13    CookVegetable(vegetable, Bake, timeInMinutes);
14}
15 
16public void CookVegetable(Vegetable vegetable,
17   Action<Vegetable, CookingTime> cookingAction,
18   int timeInMinutes)
19{
20    Clean(vegetable);
21    cookingAction(vegetable, Minutes.Is(timeInMinutes));
22    Serve(vegetable);
23}
We got rid of the duplicated code the same way we did when we did our first refactor, except this time we parameterized method calls instead of data.
If you understood this, you understand Action<>.  Action<> is just a way of treating methods like they are data. Now you can extract all of the common logic into a method and pass in data that changes as well as actions that change.
Congratulations, you are doing the strategy pattern without having to create an abstract base class and a huge inheritance tree!
So when you see Action<>, just think “ah, that means I am passing a method as data.”
It really is as simple as that.
Action<Vegetable, CookingTime> translated to English is: “A method that takes a Vegetable and a CookingTime as parameters and returns void.”

What about Func<>?

If you understand Action<>, you understand Func<>.
Func<X, Y, Z> translated to English is: “A method that takes an X, and a Y as parameters and returns a Z”.”
The only difference between Action<> and Func<> is that Func<>’s last template parameter is the return type.  Func<>s have non-void return values.
Bonus: Predicate<> is a Func<> that always returns a boolean.
.......................................................

An Action is a type of delegate:
  1. It returns no value.
  2. It may take 0 parameter to 16 parameters.
For example the following Action can encapsulate a method taking two integers as input parameters and returning void.

ActionCsharp2.gif

So if you have a method like below:

ActionCsharp3.gif

You can encapsulate the method Display in Action MyDelegate as below:
ActionCsharp4.gif

An Action with one input parameter is defined in the System namespace as below:
ActionCsharp5.gif

Where in T is a type of input parameter and a T object is a value passed for the parameter. 

Action with Anonymous method 

You can work with Action and anonymous methods as well. You can assign an anonymous method to an Action as below:

ActionCsharp6.gif

The above code will print 9 as output. 

Action with Lambda Expression 

Like any other delegates, an Action can be created with a lambda expression also, as below:

ActionCsharp7.gif

The above code will also print 9 as output. 

Passing Action as input parameter 

You can pass an Action as a parameter of a function also. Let us say you have a class:

ActionCsharp8.gif

And two functions called Display and Show to display Name and RollNumber of Student. 

ActionCsharp9.gif

Now you have a function where you need to pass either Display or Show. Or in other words you need to pass any function with the same signature of Display or Show. In that case you will be passing a delegate as an input parameter to the function. 

ActionCsharp10.gif

You can call the CallingAction method in Main as below:

ActionCsharp11.gif

No comments:

Post a Comment