- Introduction
- Whats a Lambda Expression?
- Whats a Generic?
- Exploring Action, Func, and Predicate
- Summary
Exploring Action, Func, and Predicate
Action, Func, and Predicate are delegates whose argument type is deferred until the point of declaration. Hence, these delegates are generic delegates. (Each of these generic delegates is also overloaded to accept one or more parameterized types.)
The value of these generic delegates is that they can be used as argument types, converting a function into a dynamic function whose behavior itself is deferred until a specific generic delegate is provided. For example, consider the Array class and the ForEach method. Array.ForEach is defined to indicate that it works on any array T[] and accepts the generic delegate Action<T>. This means that Array.ForEach is simply predefined to iterate over any array and do something, but the data type of the array is deferred, and what happens for each iteration is deferred until Action<T> is provided. The result is a completely dynamic capability.
- Action<T> is a generic delegate that accepts one or more parameterized types and returns void.
- Func<T> is a generic delegate that accepts one or more parameterized types and returns a parameterized type, like Action with a non-void return value.
- Predicate<T> accepts one parameterized type and returns a Boolean indicating whether the argument passes some predicate test, such as y > 5.
Listing 1 demonstrates each of the generic delegates. The first example defines an Action<Point> that prints the string representation of a point (note that PrintPoint is used just like a function). The second part defines a predicate that returns a Boolean indicating whether the x and y points are equal. The third part defines a Func<string, string[]> that accepts a string input and splits the string into an array of strings.
Listing 1 Examples demonstrating Func<T>, Action<T>, and Predicate<T>.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace ProgrammingWithGenericDelegates { class Program { static void Main(string[] args) { Point[] points = new Point[]{ new Point(3,3), new Point(4,4), new Point(5,5), new Point(6,7) }; Action<Point> PrintPoint = p => Console.WriteLine(p); Array.ForEach(points, PrintPoint); Console.WriteLine(); Predicate<Point> XYEqual = p => p.X == p.Y; Array.ForEach(points, p => Console.WriteLine("{0} {1} equal", p, XYEqual(p) == true ? "are" : "are not")); Console.WriteLine(); Func<string, string[]> Split = s => s.Split(new string[]{",", " ", "."}, StringSplitOptions.RemoveEmptyEntries); string fellini = "All art is autobiographical. The pearl is the oyster’s autobiography."; Array.ForEach(Split(fellini), s=>Console.WriteLine(s)); Console.ReadLine(); } } }
The parameter name T is used by convention, but the parameter name can be anything. Also, note that the code in Listing 1 uses the long version of the declaration, but generic delegates can be defined at their point of use. For example, the Action and first Array.ForEach pair of statements could be rewritten like this:
Array.ForEach(points, p => Console.WriteLine(p));
The preceding statement demonstrates how I would usually write the code.