Delegates, Anonymous methods and Lambdas
In the past I'd always dismissed Anonymous methods and lambdas as needless syntactical sugar. After all 'Real' programmers are the ones who think in assembler, right? As time has worn on I can see that there is a lot to the .NET framework that I haven't fully explored and would like to get to know.
This article is here to help remind me of what I know about delegates help keep me in context as I learn the more esoteric parts of the .NET platform.
References:
- Delegates
- Delegate Type [msdn.microsoft.com]
- Delegates (15.1 -> 15.3) [msdn.microsoft.com]
- Delegates vs Interfaces [msdn.microsoft.com]
- Delegates Tutorial [msdn.microsoft.com]
- Anonymous Methods, Funcs and Lambdas
- Anonymous Methods [msdn.microsoft.com]
- Func vs. Action vs. Predicate [stackoverflow.com]
- C# 3.0 Tutorial (LINQ) [programmersheaven.com]
- Practical introduction to Lambda Expressions [rvenables.com]
- Explaining what Action<> and Func<> are [simpleprogrammer.com]
About Delegates
In its most basic form a Delegate is an object which contains a function that matches a defined signature (See the links above in the Reference section for more detail). A delegate can be passed as an argument to a method and can be useful in callback situations where you may want to change what method gets executed during the callback. Delegates can be created with pre-defined methods or the methods can be created on the fly (inline).
Delegate with Pre-defined methods:
// Delegate signature internal delegate int Consume(string str); // Returns an int, single argument (string) // A sample method that knows how to use a Delegate to perform work // This method wants a delegate that matches the signature type of Consume internal void PerformAction(string name, Consume action) { // Perform runtime specific work by calling the Delegate // The delegate can point to different methods // depending on what sort of work you need to get done action(name); } // Method which matches the delegate signature we defined above static internal int Action(string username) { Console.WriteLine("hi there " + username); return 0; }
// Alternate Method which matches the delegate signature we defined above static internal int Action2(string username) { Console.WriteLine("Did you finish your homework, " + username + "?"); return 0; }
// Program entry point static void Main(string[] args) { // Call a method that accepts a delegate, let it do its thing PerformAction("billy", new Consume(Action));
// Call the same method, give it a different delegate this time
PerformAction("billy", new Consume(Action2));
}
Console Output from the above code:
hi there billy
Did you finish your homework, billy?
Delegate with inline method:
// Delegate signature internal delegate int Consume(string str); // Returns an int, single argument (string)
// Program entry point static void Main(string[] args){ // Define a method in-line // This does not require a method to be pre-written // (as was the case above with 'Action' and 'Action2' Consume d = delegate(string t) { Console.WriteLine(t); return 0; }; // Execute the delegate as if it were any old method d("echo this to console");
}
Console output:
Delegate with inline method, chained
// Delegate signature
internal delegate int Consume(string number);
// Program entry point
static void Main(string[] args)
{
// Define a method in-line // This does not require a method to be pre-written // (as was the case above with 'Action' and 'Action2' Consume d = delegate(string t) { Console.WriteLine(t); return 0; }; d += delegate(string t) { Console.WriteLine("Wow, 2 delegates executed!"); Console.WriteLine("You've heard this already: " + t); return 0; }; // Execute the delegate d("echo this to console"); }
Console Output
A few things to remember:
- The Method that a delegate object contains must match the signature of the delegate.
- In the first example above we encapsulate the int Action(string username) method which matches the Delegate definition
- Delegates (while pretty cool) are not always the best thing to employ. If you know the method you want to call at certain time it doesn't make sense to refactor the solution just to use a delegate
- When writing a method that accepts a delegate as a parameter, be sure to specify the proper delegate signature. You may have noticed that we used the parameter type Consume instead of Delegate. This ensures that whatever delegate you pass conforms with a specific delegate definition and that your method will know what to do with it.
- Delegates can be chained. Each method referenced in the delegate will execute with the given parameter(s)
- Delegates can be defined using inline (anonymous) methods
About Func<>, Action<> and Predicate<>
The Func<> datatype lets you encapsulate an anonymous method and use it in other places in your code. A Predicate is a type of Func that always returns a boolean. An Action<> is like a Func except it does NOT return a datatype (returns void). Here is what they look like:
// A Func defined by an anonymous method // Note how the return type is inferred from the anonymous code-block Func StringParser = delegate(string input) { return 0; }; // A Predicate defined by an anonymous method // Predicates are Funcs that ALWAYS return a bool // Visual studio will show an error if a code path // does not return a bool Predicate StringEvaluator = delegate(string input) { if (input.Length > 0) { return true; } else { return false; } }; // An Action defined by an anonymous method // Actions do not return anything (void) // So their signature is a bit simpler Action IntChecker = delegate(int mpg) { if (mpg > 50) { Console.WriteLine("Awesome MPG!"); } else { Console.WriteLine("Not so Awesome MPG..."); } }
Here is a summarized explanation of what you are seeing. Refer to the References section at the top of the article for more details:
- When you define a Func you define the input parameter types and the return type together like this:
- Func
- This wants a method that accepts an int and a string which returns a bool type.
- Actions and Predicates are a little simpler in that you only define the input parameters:
- Action>
- This Action definition wants a bool and a List of ints. It will return void.
- Predicate>
- A Predicate will only accept one argument. In this example it wants a List of strings
- Predicates are special forms of Func that specificall have use in certain Find method overloads on collections
About Lambdas
Lambdas are syntactic sugar which can used to take the place of anonymous function syntax and can also be used in Linq. For example:
// Linq query. var Found = from o in Orders where o.CustomerID == 84 select o.Cost;
The above LINQ query can be read like this:
- For each item 'o' in the Orders collection/list
- Select all items which have a CustomerID of 84
- Then return only the cost associated with each Order
With Type-inference they can be a little difficult to read until you get the hang of them. You can also do more complex lambdas which have additional conditions and return more than one attribute
Lambdas and Predicate methods:
One nice thing about Lambda expressions is that they make it easy to replace the sometimes-cumbersome anonymous function (delegate) syntax needed for Predicate methods like Find and FindAll. Here is an example code-snippet which shows the anonymous function syntax then a lambda:
static void Main(string[] args) { // List of strings representing various positions in a company
var positions = new List<string> { "Program manager", "Project Manager", "Scrum master", "Scurrvy dog", "Manager", "Engineer I", "Engineer II" }; // use the List.Find method to find and return the first string // entry that matches the search function string found = positions.Find(delegate(string stringEntry) { if (stringEntry.StartsWith("S")) { return true; } return false; }); // Use the List.FindAll method to find and return all string // entries that match the search function // (This one is in Lambda form) List<string> foundAll = positions.FindAll(o => o.StartsWith("E")); }
Results from the above code:
- The found string will be set to Scrum master, as that is the first string found
- The foundAll List<string> will be set to "Engineer I", "Engineer II"
Takeaway points:
- Type inferrence is the name of the game when working with Lambdas. Here is an explanation of what the Lambda is doing in the example above:
- o is determined to be of type string since that is the type the list positions holds
- => can be read as 'goes to' (so they tell me). It means that the parameters defined on the left side are to be used in the right side code-block for decision making
- In the example above, the lambda is taking the place of a Predicate, so it can only have one argument and must return a bool.
- As compared to the Find example, the lambda is more concise. This makes it more difficult to read if you are unfamiliar with Lambdas.
- A Lambda can have more than one parameter and execute more than one statement of code by using a syntax like this:
positions.Where((o, u) => { if (o == "danny") { if (u == 42) { Console.WriteLine("This is: freddy"); return true; } } return false; }); - You can have more than one parameter by wrapping all parameters in parenthesis
- More complex code blocks can be expressed by wrapping the code in Curly braces
The purpose of this article is to provide me with a quick reference of what these things are supposed do so I can be on the lookout for ways of applying them in my coding projects. Also, by writing down in my own words what I have studied I have further cemented these ideas into my brain. If you have any thoughts or suggestions, please leave them in the comments below.