flow.
"Flow is a condition of deep, nearly meditative involvement." - Tom DeMarco

The Action Delegate

Thursday, March 08, 2007 2:42 PM

Last time we talked about the Predicate object and how we could use it to identify which items of an array match our qualifications.  Today we're going to talk about the Predicate's not-so-distant cousin, the Action object and how we can quickly perform an operation against every element in an array.

Action, like Predicate, is simply a delegate that we can pass into certain functions.  Whatever 'action' is specified in Action, is performed on each member of the given collection.  Taking our litter of Cat objects, we can pass our Pet delegate to each member of the litter which will 'Pet' each Cat.

{

        Cat[] litter = new Cat[] { new Cat("Lucky"), new Cat("Frisky"), new Cat("Clyde") };

        Array.ForEach<Cat>(litter, new Action<Cat>(Pet));
}

private void Pet(Cat cat)
{

         Console.WriteLine("Pet " + cat.Name);
}

The code you see above will produce the following output (note the triumphant return of Lucky and Frisky)...

Pet Lucky

Pet Frisky

Pet Clyde

As you can see, by simply wrapping the Pet method in an Action delegate and passing it to our ForEach method we were able to perform the action to every object in the litter array.

That's all there is to it.  But wait, Pet is kind of a simple method, could we avoid the code bloat of writing an entirely new method just to perfom Pet?  Sure we can!  We can simply create an anonymous method inline of our ForEach() arguments...

Array.ForEach<Cat>(litter, delegate(Cat cat)

{

         Console.WriteLine("Pet " + cat.Name);
});

Voila!  Now we have our code written inline.  And with C# 3.0 slowly making its way into many developer's hands, where there's an anonymous delegate...there's a lamba expression!

 Array.ForEach<Cat>(litter,

      cat => Console.WriteLine("Pet " + cat.Name);

All three of these examples produce the exact same output.

There's something else here that's worth mentioning, however.  We typically use an Array for each of these examples, but these delegates are not restricted for use only on a classical array.  Both the FindXXX methods we discussed last time as well the ForEach method are also available off of the more commonly generic List<T> class.  This means that its incredibly easy to incorporate these new techniques in your real world collections!

kick it on DotNetKicks.com

Feedback

 re: The Action Delegate

"But wait, Pet is kind of a simple method, could we avoid the code bloat of writing an entirely new method..."

I assume that you mean C# code bloat. When using anonymous delegates or lamba expressions, the compiler will create an appropriate method hook it all up equivalent to using the tradition delegate declaration.
3/11/2007 11:36 PM | namae

# re: The Action Delegate

Yes, my apologies. Thank's for the clarification.

You're completely correct, anonymous delegates and lambda expressions are simply syntactic sugar that is applied at the C# code level. They're purely for the benefit of the programmer. Once the compiler gets a hold of it, it's all traditional delegates under the hood (at the IL level).

Thanks for catching that! 3/12/2007 7:35 AM | Jeremy

# The Action Delegate

You've been kicked (a good thing) - Trackback from DotNetKicks.com 3/12/2007 7:39 AM | DotNetKicks.com

 re: The Action Delegate

My problem is if i want to break the loop after certain condition match, how can i do that? i tried but i m missing something. 3/12/2007 8:14 AM | SA

 re: The Action Delegate

I really don't see how this is any better at all than the standard way:

foreach(Cat c in litter)
{
Console.WriteLine("Pet " + cat.Name);
}

Without lambda expressions by my count it takes 21 more characters to do it this way and decreases readability in the process. With lambda it still takes 6 more characters and readability takes (arguably) an even bigger hit.

I'm not sure if this is a deficiency with your example or if this delegate just isn't all that useful without lambdas, LINQ, etc. 3/12/2007 4:47 PM | skain

# re: The Action Delegate

C# 2 can do this pretty nice and concise:

Cat[] cats = ...;
Array<Cat>.ForEach(cats, Pet);

void Pet(Cat catToPet)
{
...
}

C# nicely infers that the Pet method fits the Action delegate signature, making the resulting code quite easy to read. 3/12/2007 6:23 PM | Judah

# re: The Action Delegate

Thanks for the pointer, Judah. You're completely correct. The automatic inference of the delegate cleans the code up nicely :) 3/12/2007 9:06 PM | Jeremy

# re: The Action Delegate

Skain, thanks for the comment. You're right on both counts, the lambda is much cleaner than the anonymous delegate and the example is a bit deficient :o)

On the other hand, I'm not a huge fan of enclosing over complicated methods in anonymous delegates, either. Looks like the usefulness of this without the advantage of lambdas it definitely arguable, in cases. 3/12/2007 9:09 PM | Jeremy

# re: The Action Delegate

"My problem is if i want to break the loop after certain condition match, how can i do that? i tried but i m missing something."

That's a really good question, SA. And one that I hadn't thought about :o)

I was hoping to have an answer for you by tonight...but so far I haven't cracked it yet. When I do I'll make an addendum to the post and republish it.

Anyone else out there want to take a crack at this one? 3/12/2007 9:21 PM | Jeremy

 re: The Action Delegate

My workaround is using Array.Find<Cat>(cat, PetSmallCat );
private bool PetSmallCat(Cat cat)
{
if( your break condition )
return true;
else
return false;
} 3/12/2007 11:53 PM | cpon

# re: The Action Delegate

The main benefit of this approach is that you can encapsulate some kind of logic in a named entity - Action delegate.
The whole article is an example of a Visitor pattern which by definition is used for adding new operations to an existing class without changing the class and works on object structures.
In the example above we have a simple array, but imagine that it is not an array but a Tree with a foreach method accepting an Action delegate. Does it sound more interesting?
3/13/2007 4:20 AM | Michal Talaga

# re: The Action Delegate

Nice job, Cpon. We could just create a collection containing only the items that meet our criteria. I suppose we could also do something similar with a custom iterator.

I have a feeling that you've run into the same issue that I have which is why you broke it out into a separate statement. The 'break' keyword in C# only works for breaking out of loops. The code in ForEach is executed like a loop, but isn't really a loop in the classical sense. Therefore if we try to break there's no loop to break out of. I think you may be on to something with just dealing with the items in the collection before hand. 3/13/2007 8:58 AM | Jeremy

# re: The Action Delegate

Nice observation, Michal. I hadn't picked up on that, actually.

Applying this to a tree sounds very very cool. That may be an idea for a future post :)

At the very least, it gives me something to play with over lunch today :) 3/13/2007 9:00 AM | Jeremy

Post a comment





 

Please add 7 and 6 and type the answer here: