I have been a .net developer for quite some time now and there are things which I have just blindly implemented by referring to code snippets without caring about their underlying intricacies, one of them being LINQ. I’m sure we all know what it stands for and why it is used so I won’t be going there. What I really want to discuss here is the LINQ fluent functions which we use on a family of IEnumerables
.
I always felt lazy to write these queries on my own and would always turn to online examples. I never bothered to look at the overloads and understand them. No more.Here is my very first attempt at writing a post.
To write a LINQ query efficiently there are three things you must be aware of
- Delegates
- Lambda Expressions
- variety of extension methods offered on the Enumerable class
I would like to limit this post to dissect the most commonly used "Where" extension method. The documentation for it as below
The first parameter preceded by "this" keyword tells us right there itself that it is an extension method. Also notice that it returns an IEnumerable<TSource>
. So Where is an extension method on a collection which is of type IEnumerable<TSource>
which also returns an IEnumerable<TSource>
. The collection referred here could be any collection which implements the IEnumerable interface e.g. List, IList.
We move to the second parameter of the method Func<TSource,bool> predicate
. Func is a built-in delegate in .NET.
Delegate is a type to represent a method signature. E.g. if we have a method
public string FormatNumber(int number)
then we would write its “delegate” type as
public delegate string FormatNumberDelegate(int number)
Here the FormatNumberDelegate is a type that would represent the method FormatNumber or any method which accepts an integer and returns a string. If we were to represent this as Func then the resultant delegate would be
Func<int,string>
i.e. "Func" is any method which takes in an integer parameter and returns a string.
Going back to our Where extension, Func<TSource,bool>
represents any method which takes in a parameter of TSource
and returns a boolean. The boolean returned is essentially the condition which we would like to evaluate. If from a list of Employees, we want to filter the female employees, then we would write a condition which evaluates to true (a boolean) when the employee is a female.
Lets say we have a class Employee
as follows
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Designation { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
F,
M
}
so our condition would be
if(employee.Gender == Gender.F) return true;
The simplest way to write this is
bool FilterFemaleEmployees(Employee employee)
{
if (employee.Gender == Gender.F)
return true;
return false;
}
and then we can use it with our Where extension as var femaleEmployees = employees.Where(FilterFemaleEmployees);
Comparing this with the documentation for the Where extension,
TSource = Employee
IEnumerable<TSource>
= var femaleEmployees is IEnumerable<Employee>
which is returned by the Where method
Func<TSource,bool>
= bool FilterFemaleEmployees(Employee employee)
Lets take it to one level up by making use of anonymous delegate.
The FilterFemaleEmployees is a named delegate which is good but while using LINQ the main focus is on compact queries , writing efficient code with as few lines as possible.
With an anonymous delegate, the query would look like
var femaleEmployees = employees.Where(delegate (Employee employee)
{
if (employee.Gender == Gender.F)
return true;
return false;
});
whenever we are evaluating a condition, its result is going to return a boolean. i.e if we just return employee.Gender == Gender.F , it is going to return a true or a false based on the value of employee.Gender. So reducing the code further,
var femaleEmployees = employees.Where(delegate (Employee employee)
{
return employee.Gender == Gender.F;
});
Now comes the lambda ! Lambdas are a great way to represent an anonymous function. There are three parts to a lambda expression :
the parameters, the => operator and the method body (statement or expressions).
We remove the delegate keyword and replace it with the following
var femaleEmployees = employees.Where( (Employee employee)=>
{
return employee.Gender == Gender.F;
});
Another good feature of a lambda is the parameter inference. In the above, we are supplying a parameter of type Employee to the anonymous function. But here we can make good use of the parameter inference and let the compiler figure out its type (another step towards compact code)
var femaleEmployees = employees.Where( (employee)=>
{
return employee.Gender == Gender.F;
});
If you hover over employee in visual studio, you would see the compiler has rightly interpreted its type
Another cool feature of lambda expressions is if we have a single parameter we don't need to enclose it in brackets, we can get rid of those
var femaleEmployees = employees.Where( employee=>
{
return employee.Gender == Gender.F;
});
More to compact coding !!
If we are having just a single statement and returning a single value we can well get rid of the curlys and the return statement too !
var femaleEmployees = employees.Where( employee=> employee.Gender == Gender.F);
And since we are inclined to have as much less written code as possible, a convention followed while writing lambda expressions is to name the parameters with single letters further reducing our code to
var femaleEmployees = employees.Where( e=> e.Gender == Gender.F);
And that's it ! That's how we reach the where of "Where" came from ! If you understand the Func delegate and the lambdas ,it is going to be easy to decipher and use those LINQ extension methods. You could evaluate even more complex conditions through the Where method.
A similar understanding can be extended to write the following extension methods as well
Any
public static bool Any<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);
FirstOrDefault
public static TSource FirstOrDefault<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);
SingleOrDefault
public static TSource SingleOrDefault<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate);
I would have loved to discuss about Join and the GroupBy but I will save it to a later post.
Top comments (6)
Great first post Vidisha! It’s really well explained.
Thank you Katie :) Glad you liked it !
I know there is a way using Expression Trees to expand LINQ. I would love to see a post on how to do that.
Yes ! Great suggestion . Will try to put together something on that.
amazing post. I liked vidisha how well and simply you explained it thumbs up !!
Thank you Majid ! :)