Definition
A Lambda Expression is basically a logic evolution of the concept of anonymous functions, which was introduced with C# 2.0. Anonymous functions basically allow the body of a function to be written "in-line", where we would normally expect a delegate to be invoked.
Lambda expressions provide a much more concise, functional and targeted syntax for writing anonymous methods. In LINQ they are used all the time. A LINQ query is basically a tree of Lambda expressions. We will look at expressions trees in detail in the next post in this series.
Lambda expressions provide a very compact and type-safe way to write functions that can be passed as function arguments to another function for subsequent evaluation.
A Bit of history
In this section, we will look at the evolution in C# from delegates, to anonymous methods and finally to Lambda expressions. The code for this section can be found in the "SimpleExamples" project of the download code for this article.
In this "history" section, we will take a simple problem, which in this case is the indirect execution of a block of code, and solve it using different methods:
- Delegates
- Anonymous Methods
- Lambda Expressions
Delegates
Since it inception C# and .NET 1.0 have supported the concept of a delegate. A delegate is basically a type-safe function pointer. Delegates specify a method to call and optionally an object to call the method on. Typically, delegate are used to implement callbacks and event listeners.
A simple example of a class that uses a delegate is shown below (this class is part of the code project mentioned earlier):
1 /// <summary>
2 /// This class invokes a method through a delegate
3 /// </summary>
4 public class ClassWithDelegate
5 {
6 // This is our delegate declaration
7 private delegate void MyDelegate(int myInt, string myString);
8
9 // This is the method which will invoke "Bar" through a delegate
10 public void InvokeMethodThroughDelegate()
11 {
12 MyDelegate myDelegate = new MyDelegate(Bar);
13 myDelegate(10, "test");
14 }
15
16 private void Bar(int anInt, string aString)
17 {
18 Console.WriteLine("\tThis is a method invoked through a delegate, params: {0} and {1}",
19 anInt, aString);
20 }
21
22 } // class ClassWithDelegate
First, on line 7 we have our delegate declaration, which basically specifies the name of our delegate and the signature of the to-be invoked method. The method we want to invoke is Bar, the body of which is specified from line 16 through line 20 in the code.
In line 12, notice how we have to "new up" a new instance of our delegate, specifying the name of the method (Bar) as an argument. In the next line, we can then call the method through the delegate, passing in the function arguments.
Note that delegates are type safe, so if at line 12, I specified a method (say Foo) as the new argument, with a signature that did not match the signature specified by the MyDelegate specification, I would have received a compiler error:
Error 1 No overload for 'Foo' matches delegate SimpleExamples
While delegates definitely have their use, and will continue to serve us well in the future, in the above example the code is pretty verbose. Basically, all we want to do is execute a block of code, but I need my delegate declaration, my Bar method, I need to new up a Delegate instance before I finally can invoked the code through the delegate. Isn't there a better way? Yes, there is.. enter: "Anonymous Methods"!
Anonymous Methods
An anonymous method is a new feature in C# 2.0 that lets you define an anonymous (that is, nameless) method that can be called by a delegate. Just like a regular method, a anonymous method can take any number of parameters. An anonymous method is defined inline, and not as a member of any class.
Anonymous methods can be used anywhere that a delegate is expected. You can pass an anonymous method into any method that accepts the appropriate delegate type as a parameter.
Going back to our example, here is the code for the "ClassWithAnonMethod" class with our sample project:
1 public class ClassWithAnonMethod
2 {
3 // This is the delegate declaration
4 private delegate void MyDelegate(int myInt, string myString);
5
6 // This is the method which will calls an anonymous method
7 // through a delegate
8 public void InvokeMethodThroughAnonMethod()
9 {
10 MyDelegate myDelegate = delegate(int anInt, string aString)
11 {
12 Console.WriteLine("\tThis is the anonymous method, params: {0} and {1}",
13 anInt, aString);
14 };
15
16 myDelegate(10, "test");
17 }
18
19 } // class ClassWithAnonMethod
20
As you can see, our delegate declaration is still there, but our "Bar" method has now been replaced with an anonymous method, defined in lines 12 and 13. The anonymous method itself is still invoked through the delegate in the same way at line 16. This code is a lot more compact and easier to read in some situations (as long as the code in the anonymous method is limited to a couple of lines of code).
A Zen moment...
My last comment (about anonymous functions) again makes me think that it would be a good idea to write a closure post at the end of this series, where we do kind of a "reset", and look at where it would be appropriate to use each feature mentioned in this series. In the programming world, you basically have two types of people (I am over-simplifying here, I know):
- The first type are the folks who love all of this "new stuff", and would go out of their way to use every feature possible.
- The second group of folks just wants to get the job done. From these folks you often hear comments like: 'this is very cool stuff, but it just need to get this project done, and this stuff is not going to help me get there any faster!"
I think both groups of people have a point, and the "sweet spot" is probably somewhere in between both opinions. I don't see myself driving to work every day, thinking: "Today I am going to write a killer lamba expression tree, and create a cool new query syntax" ;-), but I can see a use for each of the features we talk about so far, as long as it is applied in the right context. And of course, when it comes to LINQ, DLINQ and XLINQ itself, I think those feature will indeed change the way we work, and they will be incredible time savers indeed!
Ok, that was my short "Zen moment", sorry for the interruption, back to Lambda expressions" now!
Lambda Expressions
Lambda expression provide a more concise, functional syntax for writing an anonymous method. Compared to anonymous methods they provide a very compact and more type-safe way to write small functions that can be passed as arguments for subsequent evaluation. The Select, Where, Sort etc. clauses in LINQ are basically all written as Lambda expressions.
Below is a small code sample. This is the "ClassWithLambda" class from out sample project:
1 public class ClassWithLambda
2 {
3 // This is the delegate declaration
4 private delegate void MyDelegate(int myInt, string myString);
5
6 public void InvokeMethodWithLambdaBLOCKED EXPRESSION
7 {
8 // Now our delegate uses a Lambda expression
9 MyDelegate myDelegate =
10 (value1, value2) => Console.WriteLine(
11 "\tThis is lambda expression, params: {0} and {1}", value1, value2);
12
13 myDelegate(10, "test");
14 }
Note that we still have our Delegate declaration at line 4. The Lambda expression itself is on lines 10 and 11.
In C# 3.0 a lambda expression is syntactically written as a parameter list, followed by a =>token, and then followed by the expression or statement block to execute when the expression is invoked. In it simplest form, a lambda expression can look like this:
params => expression
You can omit the parenthesis around the parameters when you only have one parameter.
In our case, our lambda expression looks like this:
(value1, value2) => Console.WriteLine(....)
Note that I did not bother to specify the types of both value1 and value2. Now, there is nothing that prohibits me from doing so, so I could have written:
(int value1, string value2) => Console.WriteLine(....)
In the first example, the compiler will use type inference to find out what the types of passed-in parameters are, so the run-time overhead for both types will be exactly the same.
The expression you write at the right side of the => token can be any expression or statement block, and it can consume the passed-in parameters.
Now, you might look at this and say "big deal", now what does this offer me that I cannot do with anonymous method? Well, let's take a look at that in the next section!
Using Lambda Expressions as Parameters to Functions
This this section, we will look at one of the most powerful features of lambda expressions, which is its ability to be passed as parameters to functions in a type-safe way with automatic type inference. During this section, we are assuming that we are creating a Math Library, and we are going to pass in a function that performs the actual math operation as a lambda expression. We will start out simple, and add more powerful features as we go along.
Using hard-coded argument types
Below is our first example:
1 // This function takes a lambda as parameter with hard-coded
2 // types for the parameters (in this case all integers)
3 public int PerformMathFunction(Func<int, int, int> lambdaExpression, int val1, int val2)
4 {
5 int result = lambdaExpression(val1, val2);
6 Console.WriteLine("\tPerformMathFunction, val1: {0}, val2: {1}, result: {2}",
7 val1, val2, result);
8
9 return result;
10 }
the parameter:
Func<int, int, int> lambdaExpression
specifies that we are passing in a lambda expression, which takes two integers (the first two parameters are integers), and returns an integer. In a Func specification like this, the last type is always the return type. So far example an expression like this:
Func<int, double> lambdaExpression
specifies that we are passing in a lambda expression that takes one parameter that is an integer, and returns a double, or even simpler:
Func<decimal> lambdaExpression
specifies that we are passing in a lambda expression that returns a double.
We are invoking the passed-in lambda expression as follows:
int result = lambdaExpression(val1, val2);
In this case we know that our lambda expression is returning an integer, so we can assign the result of our expression to an integer. The rest of the above method simply prints our the passed-in values and the result, and returns the result.
Below is an example of how you invoke the PerformMathFunction() method:
1 // In the remainder of this method, we show how you can pass lamda
2 // expressions as arguments to a method
3 ClassWithLambdaExpressionAsParam paramClass =
4 new ClassWithLambdaExpressionAsParam();
5
6 // Simple examples without type inference
7 paramClass.PerformMathFunction((int val1, int val2) => val1 + val2, 10, 20);
8 paramClass.PerformMathFunction((int val1, int val2) => val1 * val2, 10, 20);
The lambda expressions that we pass in are:
(int val1, int val2) => val1 + val2 -and-
(int val1, int val2) => val1 * val2
The above example produces the following output:
PerformMathFunction, val1: 10, val2: 20, result: 30
PerformMathFunction, val1: 10, val2: 20, result: 200
While the above example nicely illustrates how we can pass in a lambda expressions into a method, it has one main drawback: It only works with arguments of type Integer. In the next example, we will see how we can get around this.
Using Generic arguments Types
Below is a new version of our method:
1 // This function uses Type Inference ("T"), but forces all parameters to be of the
2 // same type, so both math parameters and the return type are of the same Type T
3 public T PerformMathFunctionWithTypeInference<T>(Func<T, T, T> lambdaExpression, T val1, T val2)
4 {
5 T result = lambdaExpression(val1, val2);
6 Console.WriteLine("\tPerformMathFunctionWithTypeInference, val1: {0}, val2: {1}, result: {2}",
7 val1, val2, result);
8
9 Console.WriteLine("\t\tType of T: {0}", typeof(T).Name);
10
11 return result;
12 }
Now, our passed-in lambda expression has changed to:
Func<T, T, T> lambdaExpression
Now, we are using a generically typed parameter, and the compiler will use type interference to determine what the type of T is. Note that we used the same type "T" everywhere, so we expect both input arguments and the return type to be of the same type. Compared to our previous example, our method has become a lot more flexible though, since we now can pass in an argument of any type, and the compiler will do the work of figuring out what the type is.
Note also that since our Method PerformMathFunctionWithTypeInference() now basically is a generic method, we need to specify the generic parameter <T> as part of its name:
public T PerformMathFunctionWithTypeInference<T>(...)
Note that our method also prints out the name of the type "T", so in our output, we will be able to verify that the compiler indeed inferred the correct type.
So, if we call this method as follows:
1 // In the remainder of this method, we show how you can pass lamda
2 // expressions as arguments to a method
3 ClassWithLambdaExpressionAsParam paramClass =
4 new ClassWithLambdaExpressionAsParam();
5
6 // Advanced examples with type inference
7 paramClass.PerformMathFunctionWithTypeInference((val1, val2) => val1 / val2, 200.0, 5.0);
Our output will look as follows:
PerformMathFunctionWithTypeInference, val1: 200, val2: 5, result: 40
Type of T: Double
Because we passed in variables 200.0 and 5.0, the compiler correctly inferred that the type of T is indeed Double.
In our next example, we will take this example one step further, and vary the output type of the result.
Using different Generic types for the Arguments
As a next step, we will extend our example once more, and allow the return type of our lambda function to be different then the type of the passed-in parameters. The code for the example is shown below:
1 // This function also takes a lambda parameters, but it allows the caller to specify a
2 // different type for the return type of the lambda (type "R"). Both parameters are expected
3 // to be of the same type "T"
4 public R PerformMathFunctionGenericFlexReturnType<R, T>(Func<T, T, R> lambdaExpression, T val1, T val2)
5 {
6 R result = lambdaExpression(val1, val2);
7 Console.WriteLine("\tPerformMathFunctionGenericFlexReturnType, val1: {0}, val2: {1}, result: {2}",
8 val1, val2, result);
9
10 Console.WriteLine("\t\tType of T: {0}, Type of R: {1}", typeof(T).Name, typeof(R).Name);
11
12 return result;
13 }
Notice the signature of our lambda expression:
Func<T, T, R> lambdaExpression
Indicating that the return type of our lambda expression is now the generic type "R", while the input parameters have the generic type "T". Notice also how we had to add the additional generic type parameter to our function signature:
public R PerformMathFunctionGenericFlexReturnType<R, T>(....)
In our function, we can now use the return type "R" as the return type of the lambda expression. In this case, we also selected to print out both the name of the types of "T" and "R". An example invocation method is shown below:
1 // In the remainder of this method, we show how you can pass lamda
2 // expressions as arguments to a method
3 ClassWithLambdaExpressionAsParam paramClass =
4 new ClassWithLambdaExpressionAsParam();
5
6 paramClass.PerformMathFunctionGenericFlexReturnType((val1, val2) => (int)(val1 / val2), 5.0, 2.0);
So, our lambda expression is in this case:
(val1, val2) => (int)(val1 / val2)
When we run this sample, we get the following output:
PerformMathFunctionGenericFlexReturnType, val1: 5, val2: 2, result: 2
Type of T: Double, Type of R: Int32
Because our passed-in lambda expression did a cast of the result to integer, the compiler correctly inferred that the type of "R" is indeed System.Int32.
Lambda Expressions and Extension Methods
Introduction
We talked about extension methods in part 4 of this series. Extension methods can basically be defined for any type, and long as the type itself is not static. We you combine Lambda expressions with extension methods, you get a very powerful brew. It is fact this combination that is at the very core of the query syntax of LINQ.
Microsoft has create a large number of extension methods for the IEnumberable<T> type. IEnumerable<T> is a logical choice since this interface is supported by most generics collections in C#. All of these extensions are defined in the namespace System.Linq, in the static class Enumerable:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace System.Linq
{
// Summary:
// Provides a set of static (Shared in Visual Basic) methods for querying objects
// that implement System.Collections.Generic.IEnumerable<T>.
public static class Enumerable
{
// Summary:
// Applies an accumulator function over a sequence.
//
// Parameters:
// source:
// An System.Collections.Generic.IEnumerable<T> to aggregate over.
//
// Parameters:
//
// func:
// An accumulator function to be invoked on each element.
//
// Type parameters:
// TSource:
// The type of the elements of source.
//
// Returns:
// The final accumulator value.
//
// Exceptions:
// System.ArgumentNullException:
// source or func is null.
//
// System.InvalidOperationException:
// source contains no elements.
public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func);
......
}
}
Now, I wanted to find out how many extensions methods there were exactly, and give the reader some idea of how many different overloads there were for every method, but instead of doing this manual, I decided to write a little LINQ query (you know I had to ;-)!. This is the code:
1 public static void DetermineNumberOfExtensionMethodsForIEnumerable()
2 {
3 // Write the total number of methods
4 Console.WriteLine(
5 "Total number of methods in System.Linq.Enumerable: {0}",
6 typeof(System.Linq.Enumerable).GetMethods().Length);
7
8 // This query gets the name and number of overloaded methods
9 var overloads =
10 from method in typeof(System.Linq.Enumerable).GetMethods()
11 where method.IsStatic == true
12 group method by method.Name into m
13 orderby m.Key
14 select new
15 {
16 MethodName = m.Key,
17 OverloadCount = m.Count()
18 };
19
20 Console.WriteLine(Environment.NewLine);
21
22 // Write out the overloads
23 foreach (var overload in overloads)
24 {
25 Console.WriteLine(
26 string.Format("Method: '{0}', # of overloads: {1}",
27 overload.MethodName, overload.OverloadCount));
28 }
29
30 } // method DetermineNumberOfExtensionMethodsForIEnumerable
31
I will no go over the detailed syntax here, since we have not talked about expression trees yet, but basically first this method uses good old reflection to get the total number of methods, and next, its uses a very simply LINQ query to group the methods in System.Linq.Enumerable by method name, and selects the results in an anonymous type which contains two properties:
- The method name
- The number of overloads
It uses a simple group by and order by to group and sort the results. Look like good old fashioned SQL doesn't it?
Here are the result of the above method:
Total number of methods in System.Linq.Enumerable: 178
Method: 'Aggregate', # of overloads: 3
Method: 'All', # of overloads: 1
Method: 'Any', # of overloads: 2
Method: 'AsEnumerable', # of overloads: 1
Method: 'Average', # of overloads: 20
Method: 'Cast', # of overloads: 1
Method: 'Concat', # of overloads: 1
Method: 'Contains', # of overloads: 2
Method: 'Count', # of overloads: 2
Method: 'DefaultIfEmpty', # of overloads: 2
Method: 'Distinct', # of overloads: 2
Method: 'ElementAt', # of overloads: 1
Method: 'ElementAtOrDefault', # of overloads: 1
Method: 'Empty', # of overloads: 1
Method: 'Except', # of overloads: 2
Method: 'First', # of overloads: 2
Method: 'FirstOrDefault', # of overloads: 2
Method: 'GroupBy', # of overloads: 8
Method: 'GroupJoin', # of overloads: 2
Method: 'Intersect', # of overloads: 2
Method: 'Join', # of overloads: 2
Method: 'Last', # of overloads: 2
Method: 'LastOrDefault', # of overloads: 2
Method: 'LongCount', # of overloads: 2
Method: 'Max', # of overloads: 22
Method: 'Min', # of overloads: 22
Method: 'OfType', # of overloads: 1
Method: 'OrderBy', # of overloads: 2
Method: 'OrderByDescending', # of overloads: 2
Method: 'Range', # of overloads: 1
Method: 'Repeat', # of overloads: 1
Method: 'Reverse', # of overloads: 1
Method: 'Select', # of overloads: 2
Method: 'SelectMany', # of overloads: 4
Method: 'SequenceEqual', # of overloads: 2
Method: 'Single', # of overloads: 2
Method: 'SingleOrDefault', # of overloads: 2
Method: 'Skip', # of overloads: 1
Method: 'SkipWhile', # of overloads: 2
Method: 'Sum', # of overloads: 20
Method: 'Take', # of overloads: 1
Method: 'TakeWhile', # of overloads: 2
Method: 'ThenBy', # of overloads: 2
Method: 'ThenByDescending', # of overloads: 2
Method: 'ToArray', # of overloads: 1
Method: 'ToDictionary', # of overloads: 4
Method: 'ToList', # of overloads: 1
Method: 'ToLookup', # of overloads: 4
Method: 'Union', # of overloads: 2
Method: 'Where', # of overloads: 2
When you study this list carefully, you recognize a large number of SQL constructs, such as Union, Where, Join, Intersect, OrderBy etc..
Manually Invoking Extension Methods with Lambda Expressions
Using Type Inference
In the previous example, we jumped ahead a bit, directly to "pure" LINQ query syntax. Sorry about that, I just wanted to make sure that you guys stayed awake reading this article;-) In this section, we will take the LINQ query from the previous section, and play the role of compiler by manually writing the statement that the compiler generates for us when executing the query. Note that this will be an "unofficial" translation. The "real" translation is done with expression trees, which we will take a look at in our next post.
Let's summarize first what we need to do:
- We need to get an array of all the methods in System.Linq.Enumerable.
- Next, we filter this list by only including the static methods
- Then, we order this group by Method Name
- Next, we need to group the result of step 2 by Method Name
- Finally, we loop over each tuple in the group, and print out the method name, and the count of methods, which is basically the number of overloads for the method.
Ok, that does not look difficult at all, so let's get to work. Below is the code for the first version of this implementation:
1 public static void ExampleUsingTypeInference()
2 {
3 Console.WriteLine("\nNo query, strongly typed variables" + Environment.NewLine);
4
5 // First, get all methods in the class
6 var methods = typeof(System.Linq.Enumerable).GetMethods();
7 Console.WriteLine(
8 "Total number of methods in System.Linq.Enumerable: {0}",
9 methods.Length);
10
11 // Now, get the static methods
12 var staticMethods =
13 methods.Where(m => m.IsStatic == true);
14
15 // Now, order by name
16 var orderedStaticMethods =
17 staticMethods.OrderBy(m => m.Name);
18
19 // Next, group by name
20 var methodGroups = orderedStaticMethods.GroupBy(m => m.Name);
21
22 // Iterate over each group, print out the key (which is the Method Name, and the count of
23 // methods in the Group
24 foreach (var methodGroup in methodGroups)
25 {
26 Console.WriteLine(
27 string.Format("Method: '{0}', # of overloads: {1}",
28 methodGroup.Key, methodGroup.Count()));
29 }
30 } // method ExampleUsingTypeInference
The first important fact that you probably notice is my heavy use of type inference. I use implicitly typed local variables pretty much all over the place, because at this point, I was not interested in the detailed data types returned by each of the extension methods. That's something we worry about in the next section..
In line 6, we get the type of System.Linq.Enumerable, and use GetMethods() to get all of the methods of the type. This returns me an array of MethodInfo's, but I don't really worry about that, so I use a var for the result. It is important to know though that whatever the type is of methods, it will support the IEnumerable<T> interface (this interface is supported by pretty much all array and generic collection types in .NET).
Next, we use the Where extension method of IENumerable<T> to filter out just the static methods in lines 12 and 13. The lambda expression that we pass in is:
m => m.IsStatic == true
When the compiler looks at this lambda expression, it will use type inference to infer the following:
- Since GetMethods() returned an array of MethodInfo's, m must be of type MethodInfo.
- The expression body of our lambda expression returns a boolean, since we have a boolean expression
Again, we don't care about the return type of this method, so we use the implicitly typed local variable staticMethods to store the result.
Next, we need to order by name, another IEnumerable<T> extension method OrderBy can do this for us. The lambda expression we pass in in lines 16 and 17 specifies the Name of the method, so we are indeed sorting by method name.
In line 20 we use the GroupBy IEnumerable<T> extension method to sort the methods by name. Again, we assign this to a var (methodGroups). In a group, the identifier of the group can be accessed by the Key property (which is our case is the method name, because we grouped by method name). We can also apply aggregate functions to a group, in our case we use the Count() method in line 28 to get the count of the methods (i.e. "overloads") in each group.
When you run this code, you will get exactly the same result as the result you got with the LINQ query, which proves that our analysis process was correct. This also shows us that nothing "magical" is going on. The only real processing here was the invocation of IEnumerable<T> extension methods with very simple lambda expressions.
Using Explicit Types
Just to show how type inference makes our lives easy, I have taken the above example, but this time, I have used the "real" types returned from the extension methods, instead of relying on type inference. Below is the code:
1 public static void ExampleUsingStrongTyping()
2 {
3 Console.WriteLine("\nNo query, strongly typed variables" + Environment.NewLine);
4
5 // First, get all methods in the class
6 System.Reflection.MethodInfo[] methods = typeof(System.Linq.Enumerable).GetMethods();
7 Console.WriteLine(
8 "Total number of methods in System.Linq.Enumerable: {0}",
9 methods.Length);
10
11 // Now, get the static methods
12 IEnumerable<System.Reflection.MethodInfo> staticMethods =
13 methods.Where(m => m.IsStatic == true);
14
15 // Now, order by name
16 IOrderedEnumerable<System.Reflection.MethodInfo> orderedStaticMethods =
17 staticMethods.OrderBy(m => m.Name);
18
19 // Next, group by name
20 IEnumerable<IGrouping<string, System.Reflection.MethodInfo>> methodGroups =
21 orderedStaticMethods.GroupBy(m => m.Name);
22
23 // Iterate over each group, print out the key (which is the Method Name, and the count of
24 // methods in the Group
25 foreach (IGrouping<string, System.Reflection.MethodInfo> methodGroup in methodGroups)
26 {
27 Console.WriteLine(
28 string.Format("Method: '{0}', # of overloads: {1}",
29 methodGroup.Key, methodGroup.Count()));
30 }
31 } // method ExampleUsingStrongTyping
As you can see, no more var's in the above code. Although the code is a bit more complex, the types used make sense, and are really nothing new. The only interface that is really new to us is the IGrouping interface, which represents a query grouping:
1 namespace System.Linq
2 {
3 // Summary:
4 // Represents a collection of objects that have a common key.
5 //
6 // Type parameters:
7 // TKey:
8 // The type of the key of the System.Linq.IGrouping<TKey,TElement>.
9 //
10 // Type parameters:
11 //
12 // TElement:
13 // The type of the values in the System.Linq.IGrouping<TKey,TElement>.
14 public interface IGrouping<TKey, TElement> : IEnumerable<TElement>, IEnumerable
15 {
16 // Summary:
17 // Gets the key of the System.Linq.IGrouping<TKey,TElement>.
18 //
19 // Returns:
20 // The key of the System.Linq.IGrouping<TKey,TElement>.
21 TKey Key { get; }
22 }
23 }
From the above interface definition, you notice the read-only Key attribute, which represents the identifier of the grouping. Also, a number of overloads in IEnumerable<T> take IGrouping arguments, and allow you to apply aggregate functions to then, such as Count(), Sum(), Avg() etc.. which comes in very handy in a number of common queries.
Passing Lambda Expressions to Custom Extension Methods
When you write your own extension methods, your methods can also take lambda expressions to enhance the flexibility of the method. Below is a very simple System.String extension method:
1 public static string ManipulateCharacterWithinString(
2 this String str, Func<char, string> manipulateFunc, char ch)
3 {
4 string subStr = manipulateFunc(ch);
5 return str.Replace(ch.ToString(), subStr);
6
7 } // method DuplicateCharacterWithString
8
This extension method is very simple.It takes a lambda expression called manipulateFunc, which can translate and character into a string. The extension method will then perform a replace in the source string from the original character to the string created by the Lambda expression.
Below is an example of some invocations of this extension method:
1 Console.WriteLine("\nString extension method taking a lambda expression");
2 string str = "test";
3 string newStr =
4 str.ManipulateCharacterWithinString(
5 (charToManipulate) => charToManipulate.ToString() +
6 charToManipulate.ToString(),
7 'e');
8 Console.WriteLine("Old string: {0}, new String: {1}", str, newStr);
9
10 string otherNewString =
11 str.ManipulateCharacterWithinString(
12 (charToManipulate) => charToManipulate.ToString() + "#", 'e');
13 Console.WriteLine("Old string: {0}, new String: {1}", str, otherNewString);
14
The first lambda expression basically duplicates the passed-in character, while the second lambda expression extends the character with the "#" character.
The above sample will produce the following output:
String extension method taking a lambda expression
Old string: test, new String: teest
Old string: test, new String: te#st
and, as you can see the correct substitutions were performed.
Wrap Up
Sorry about the length of this post. We had a lot to cover here:
- First, we compared a lambda expression to classic delegates and anonymous functions
- Next, we looked at how you can pass lambda expressions as function arguments, with or without type inference.
- Finally, we looked at the powerful combination of extension methods and lambda expression, and we saw how this forms the cornerstone of the LINQ query sub-system.
In the next section, we take a look at expression trees, which is the mechanism that allows us to create a custom language syntax, such as the LINQ query syntax.
I encourage you to download the corresponding sample code, and as always, feedback is very much appreciated.