LINQ extension methods on IEnumerable<T>
take actual methods1, whether anonymous methods:
//C#
Func<int,bool> fn = x => x > 3;
var list = new List<int>() {1,2,3,4,5,6};
var query = list.Where(fn);
'VB.NET
Dim fn = Function(x As Integer) x > 3
Dim list = New List From {1,2,3,4,5,6};
Dim query = list.Where(fn);
or named methods (methods explicitly defined as part of a class):
//C#
class Program {
bool LessThan4(int x) {
return x < 4;
}
void Main() {
var list = new List<int>() {1,2,3,4,5,6};
var query = list.Where(LessThan4);
}
}
'VB.NET
Class Program
Function LessThan4(x As Integer) As Boolean
Return x < 4
End Function
Sub Main
Dim list = New List From {1,2,3,4,5,6};
Dim query = list.Where(AddressOf LessThan4)
End Sub
End Class
In theory, it is possible to parse the method's IL, figure out what the method is trying to do, and apply that method's logic to any underlying data source, not just objects in memory. But parsing IL is not for the faint of heart.
Fortunately, .NET provides the IQueryable<T>
interface, and the extension methods at System.Linq.Queryable
, for this scenario. These extension methods take an expression tree — a data structure representing code — instead of an actual method, which the LINQ provider can then parse2 and convert to a more appropriate form for querying the underlying data source. For example:
//C#
IQueryable<Person> qry = PersonsSet();
// Since we're using a variable of type Expression<Func<Person,bool>>, the compiler
// generates an expression tree representing this code
Expression<Func<Person,bool>> expr = x => x.LastName.StartsWith("A");
// The same thing happens when we write the lambda expression directly in the call to
// Queryable.Where
qry = qry.Where(expr);
'VB.NET
Dim qry As IQueryable(Of Person) = PersonSet()
' Since we're using a variable of type Expression(Of Func(Of Person,Boolean)), the compiler
' generates an expression tree representing this code
Dim expr As Expression(Of Func(Of Person, Boolean)) = Function(x) x.LastName.StartsWith("A")
' The same thing happens when we write the lambda expression directly in the call to
' Queryable.Where
qry = qry.Where(expr)
If (for example) this query is against a SQL database, the provider could convert this expression to the following SQL statement:
SELECT *
FROM Persons
WHERE LastName LIKE N'A%'
and execute it against the data source.
On the other hand, if the query is against a REST API, the provider could convert the same expression to an API call:
http://www.example.com/person?filtervalue=A&filtertype=startswith&fieldname=lastname
There are two primary benefits in tailoring a data request based on an expression (as opposed to loading the entire collection into memory and querying locally):
LastName
. Loading the objects into local memory and querying in-memory loses that efficiency.Notes
1. Technically, they don't actually take methods, but rather delegate instances which point to methods. However, this distinction is irrelevant here.
2. This is the reason for errors like "LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.". The LINQ provider (in this case the Entity Framework provider) doesn't know how to parse and translate a call to ToString
to equivalent SQL.