int[] numbers = [ 5, 10, 8, 3, 6, 12 ];
//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;
//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
foreach (int i in numQuery1)
Console.Write(i + " ");
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
Console.Write(i + " ");
The output from the two examples is identical. You can see that the type of the query variable is the same in both forms: IEnumerable<T>.
To understand the method-based query, let's examine it more closely. On the right side of the expression, notice that the where
clause is now expressed as an instance method on the numbers
object, which has a type of IEnumerable<int>
. If you're familiar with the generic IEnumerable<T> interface, you know that it doesn't have a Where
method. However, if you invoke the IntelliSense completion list in the Visual Studio IDE, you see not only a Where
method, but many other methods such as Select
, SelectMany
, Join
, and Orderby
. These methods implement the standard query operators.
Although it looks as if IEnumerable<T> includes more methods, it doesn't. The standard query operators are implemented as extension methods. Extensions methods "extend" an existing type; they can be called as if they were instance methods on the type. The standard query operators extend IEnumerable<T> and that is why you can write numbers.Where(...)
.
To use extension methods, you bring them into scope with using
directives. From your application's point of view, an extension method and a regular instance method are the same.
For more information about extension methods, see Extension Methods. For more information about standard query operators, see Standard Query Operators Overview (C#). Some LINQ providers, such as Entity Framework and LINQ to XML, implement their own standard query operators and extension methods for other types besides IEnumerable<T>.
Lambda expressions
In the previous example, notice that the conditional expression (num % 2 == 0
) is passed as an in-line argument to the Enumerable.Where method: Where(num => num % 2 == 0).
This inline expression is a lambda expression. It's a convenient way to write code that would otherwise have to be written in more cumbersome form. The num
on the left of the operator is the input variable, which corresponds to num
in the query expression. The compiler can infer the type of num
because it knows that numbers
is a generic IEnumerable<T> type. The body of the lambda is just the same as the expression in query syntax or in any other C# expression or statement. It can include method calls and other complex logic. The return value is just the expression result. Certain queries can only be expressed in method syntax and some of those require lambda expressions. Lambda expressions are a powerful and flexible tool in your LINQ toolbox.
Composability of queries
In the previous code example, the Enumerable.OrderBy method is invoked by using the dot operator on the call to Where
. Where
produces a filtered sequence, and then Orderby
sorts the sequence produced by Where
. Because queries return an IEnumerable
, you compose them in method syntax by chaining the method calls together. The compiler does this composition when you write queries using query syntax. Because a query variable doesn't store the results of the query, you can modify it or use it as the basis for a new query at any time, even after you execute it.
The following examples demonstrate some simple LINQ queries by using each approach listed previously.
These queries operate on simple in-memory collections; however, the basic syntax is identical to that used in LINQ to Entities and LINQ to XML.
Example - Query syntax
You write most queries with query syntax to create query expressions. The following example shows three query expressions. The first query expression demonstrates how to filter or restrict results by applying conditions with a where
clause. It returns all elements in the source sequence whose values are greater than 7 or less than 3. The second expression demonstrates how to order the returned results. The third expression demonstrates how to group results according to a key. This query returns two groups based on the first letter of the word.
List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
// The query variables can also be implicitly typed by using var
// Query #1.
IEnumerable<int> filteringQuery =
from num in numbers
where num is < 3 or > 7
select num;
// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num is < 3 or > 7
orderby num ascending
select num;
// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];
The type of the queries is IEnumerable<T>. All of these queries could be written using var
as shown in the following example:
var query = from num in numbers...
In each previous example, the queries don't actually execute until you iterate over the query variable in a foreach
statement or other statement.
Example - Method syntax
Some query operations must be expressed as a method call. The most common such methods are those methods that return singleton numeric values, such as Sum, Max, Min, Average, and so on. These methods must always be called last in any query because they return a single value and can't serve as the source for an extra query operation. The following example shows a method call in a query expression:
List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];
// Query #4.
double average = numbers1.Average();
// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
If the method has System.Action or System.Func<TResult> parameters, these arguments are provided in the form of a lambda expression, as shown in the following example:
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
In the previous queries, only Query #4 executes immediately, because it returns a single value, and not a generic IEnumerable<T> collection. The method itself uses foreach
or similar code in order to compute its value.
Each of the previous queries can be written by using implicit typing with var
, as shown in the following example:
// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);
Example - Mixed query and method syntax
This example shows how to use method syntax on the results of a query clause. Just enclose the query expression in parentheses, and then apply the dot operator and call the method. In the following example, query #7 returns a count of the numbers whose value is between 3 and 7. In general, however, it's better to use a second variable to store the result of the method call. In this manner, the query is less likely to be confused with the results of the query.
// Query #7.
// Using a query expression with method syntax
var numCount1 = (
from num in numbers1
where num is > 3 and < 7
select num
).Count();
// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
from num in numbers1
where num is > 3 and < 7
select num;
var numCount2 = numbersQuery.Count();
Because Query #7 returns a single value and not a collection, the query executes immediately.
The previous query can be written by using implicit typing with var
, as follows:
var numCount = (from num in numbers...
It can be written in method syntax as follows:
var numCount = numbers.Count(n => n is > 3 and < 7);
It can be written by using explicit typing, as follows:
int numCount = numbers.Count(n => n is > 3 and < 7);
Dynamically specify predicate filters at run time
In some cases, you don't know until run time how many predicates you have to apply to source elements in the where
clause. One way to dynamically specify multiple predicate filters is to use the Contains method, as shown in the following example. The query returns different results based on the value of id
when the query is executed.
int[] ids = [111, 114, 112];
var queryNames =
from student in students
where ids.Contains(student.ID)
select new
student.LastName,
student.ID
foreach (var name in queryNames)
Console.WriteLine($"{name.LastName}: {name.ID}");
/* Output:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
// Change the ids.
ids = [122, 117, 120, 115];
// The query will now return different results
foreach (var name in queryNames)
Console.WriteLine($"{name.LastName}: {name.ID}");
/* Output:
Adams: 120
Feng: 117
Garcia: 115
Tucker: 122
You can use control flow statements, such as if... else
or switch
, to select among predetermined alternative queries. In the following example, studentQuery
uses a different where
clause if the run-time value of oddYear
is true
or false
.
void FilterByYearType(bool oddYear)
IEnumerable<Student> studentQuery = oddYear
? (from student in students
where student.Year is GradeLevel.FirstYear or GradeLevel.ThirdYear
select student)
: (from student in students
where student.Year is GradeLevel.SecondYear or GradeLevel.FourthYear
select student);
var descr = oddYear ? "odd" : "even";
Console.WriteLine($"The following students are at an {descr} year level:");
foreach (Student name in studentQuery)
Console.WriteLine($"{name.LastName}: {name.ID}");
FilterByYearType(true);
/* Output:
The following students are at an odd year level:
Fakhouri: 116
Feng: 117
Garcia: 115
Mortensen: 113
Tucker: 119
Tucker: 122
FilterByYearType(false);
/* Output:
The following students are at an even year level:
Adams: 120
Garcia: 114
Garcia: 118
O'Donnell: 112
Omelchenko: 111
Zabokritski: 121
Handle null values in query expressions
This example shows how to handle possible null values in source collections. An object collection such as an IEnumerable<T> can contain elements whose value is null. If a source collection is null
or contains an element whose value is null
, and your query doesn't handle null
values, a NullReferenceException is thrown when you execute the query.
You can code defensively to avoid a null reference exception as shown in the following example:
var query1 =
from c in categories
where c != null
join p in products on c.ID equals p?.CategoryID
select new
Category = c.Name,
Name = p.Name
In the previous example, the where
clause filters out all null elements in the categories sequence. This technique is independent of the null check in the join clause. The conditional expression with null in this example works because Products.CategoryID
is of type int?
, which is shorthand for Nullable<int>
.
In a join clause, if only one of the comparison keys is a nullable value type, you can cast the other to a nullable value type in the query expression. In the following example, assume that EmployeeID
is a column that contains values of type int?
:
var query =
from o in db.Orders
join e in db.Employees
on o.EmployeeID equals (int?)e.EmployeeID
select new { o.OrderID, e.FirstName };
In each of the examples, the equals
query keyword is used. You can also use pattern matching, which includes patterns for is null
and is not null
. These patterns aren't recommended in LINQ queries because query providers might not interpret the new C# syntax correctly. A query provider is a library that translates C# query expressions into a native data format, such as Entity Framework Core. Query providers implement the System.Linq.IQueryProvider interface to create data sources that implement the System.Linq.IQueryable<T> interface.
Handle exceptions in query expressions
It's possible to call any method in the context of a query expression. Don't call any method in a query expression that can create a side effect such as modifying the contents of the data source or throwing an exception. This example shows how to avoid raising exceptions when you call methods in a query expression without violating the general .NET guidelines on exception handling. Those guidelines state that it's acceptable to catch a specific exception when you understand why it's thrown in a given context. For more information, see Best Practices for Exceptions.
The final example shows how to handle those cases when you must throw an exception during execution of a query.
The following example shows how to move exception handling code outside a query expression. This refactoring is only possible when the method doesn't depend on any variables local to the query. It's easier to deal with exceptions outside of the query expression.
// A data source that is very likely to throw an exception!
IEnumerable<int> GetData() => throw new InvalidOperationException();
// DO THIS with a datasource that might
// throw an exception.
IEnumerable<int>? dataSource = null;
dataSource = GetData();
catch (InvalidOperationException)
Console.WriteLine("Invalid operation");
if (dataSource is not null)
// If we get here, it is safe to proceed.
var query =
from i in dataSource
select i * i;
foreach (var i in query)
Console.WriteLine(i.ToString());
In the catch (InvalidOperationException)
block in the preceding example, handle (or don't handle) the exception in the way that is appropriate for your application.
In some cases, the best response to an exception that is thrown from within a query might be to stop the query execution immediately. The following example shows how to handle exceptions that might be thrown from inside a query body. Assume that SomeMethodThatMightThrow
can potentially cause an exception that requires the query execution to stop.
The try
block encloses the foreach
loop, and not the query itself. The foreach
loop is the point at which the query is executed. Run-time exceptions are thrown when the query is executed. Therefore they must be handled in the foreach
loop.
// Not very useful as a general purpose method.
string SomeMethodThatMightThrow(string s) =>
s[4] == 'C' ?
throw new InvalidOperationException() :
@"C:\newFolder\" + s;
// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];
// Demonstration query that throws.
var exceptionDemoQuery =
from file in files
let n = SomeMethodThatMightThrow(file)
select n;
foreach (var item in exceptionDemoQuery)
Console.WriteLine($"Processing {item}");
catch (InvalidOperationException e)
Console.WriteLine(e.Message);
/* Output:
Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
Operation is not valid due to the current state of the object.
Remember to catch whatever exception you expect to raise and/or do any necessary cleanup in a finally
block.
See also
Walkthrough: Writing Queries in C#
where clause
Querying based on runtime state
Nullable<T>
Nullable value types