By directly integrating query capabilities into the C# and Visual Basic computer languages, C# LINQ (Language Integrated Query) is a potent collection of extensions to the.NET Framework. It is regarded as a crucial component of C# 3.0 and was first introduced in the.NET 3.5 framework.
What is C# LINQ?
Fundamentally, LINQ offers a standardized approach for both querying and manipulating data from a variety formats. Prior to LINQ, programmers frequently had to get familiar with a variety of query languages for various data types (such as XPath/XQuery for XML and SQL for databases). LINQ streamlines this process, enabling you to work with different data types in your C# or VB.NET code using a single, well-known syntax.
Key Concepts and Features
Unified Querying: A variety of data sources can be queried with LINQ, including:
- In-memory collections: Such as arrays,
List<T>
, and other types that implementIEnumerable
orIEnumerable<T>
(known as LINQ to Objects). - SQL databases: Through technologies like LINQ to SQL or Entity Framework.
- XML documents: With LINQ to XML.
- ADO.NET Datasets.
- Web services.
Syntax: LINQ has two main query writing syntaxes:
- Query Syntax: This is easy for people who know how to query databases because it looks like a SQL query. Keywords such as from, in, where, select, orderby, group, and join are used. It’s really “syntactic sugar” because the C# compiler internally converts query syntax into method calls (Method Syntax) at compile time.
- Method Syntax: This makes use of extension methods, which are frequently chained together, on IEnumerable or IQueryable objects. It uses short anonymous functions called lambda expressions (=>) to specify the logic for data transformation, sorting, and filtering.
Core Query Operators:
- Filtering (
Where
): Chooses a subset of items from a collection according to a predetermined criterion. - Projection (
Select
): Changes the shape of elements or chooses properties, sometimes resulting in anonymous types. - Ordering (
OrderBy, OrderByDescending, ThenBy, ThenByDescending
): Sorting elements according to one or more criteria in either ascending or descending order is known as ordering (OrderBy, OrderByDescending, ThenBy, ThenByDescending). - oining (
Join
,GroupJoin
): Uses matching keys to combine elements from various collections, much like SQL joins. - Grouping (
GroupBy
): Divides components into groups according on shared characteristics. - Aggregation (
Sum
,Min
,Max
,Average
,Count
): Carries out aggregate computations on numerical data in a set. - Partitioning (
Skip
,Take
,SkipWhile
,TakeWhile
): Selects elements based on their position or whether a condition is met.
Deferred Execution: Queries are not run instantly once they are defined, which is a key idea in LINQ. Instead, they are only run when their output is truly required or “enumerated” (for example, when you specifically apply methods like ToList() or ToArray() or iterate over them using a foreach loop). This makes it possible to design queries dynamically and manage resources effectively.
Expression Trees: Expression trees are created using LINQ queries in order to access external data sources, such as databases (via IQueryable). The structure of the code is represented in-memory by an expression tree, which a LINQ provider can subsequently interpret and convert into the native query language of the data source (for example, SQL for a relational database).
Code Example
For filtering, projecting, and ordering data, the following sample illustrates LINQ Query Syntax and Method Syntax:
using System;
using System.Collections.Generic;
using System.Linq; // Required for LINQ extensions
public class LinqDemonstration
{
public static void Main(string[] args)
{
// Sample Data: A list of products with Name and Price
List<Product> products = new List<Product>
{
new Product { Name = "Laptop", Price = 1200 },
new Product { Name = "Mouse", Price = 25 },
new Product { Name = "Keyboard", Price = 75 },
new Product { Name = "Monitor", Price = 300 },
new Product { Name = "Webcam", Price = 50 },
new Product { Name = "Speaker", Price = 150 }
};
Console.WriteLine("--- Original Products ---");
foreach (var p in products)
{
Console.WriteLine($"Product: {p.Name}, Price: ${p.Price}");
}
// 1. LINQ Query Syntax Example: Get products with price > $100, ordered by price descending
Console.WriteLine("\n--- Products over $100 (Query Syntax) ---");
var expensiveProducts = from product in products // Specifies the data source and range variable
where product.Price > 100 // Filters products based on a condition
orderby product.Price descending // Sorts the results by price in descending order
select new { product.Name, product.Price }; // Projects results into an anonymous type
Console.WriteLine("Name Price");
Console.WriteLine("------------------");
foreach (var p in expensiveProducts) // Query execution happens here (deferred execution)
{
Console.WriteLine($"{p.Name,-12} ${p.Price}");
}
// 2. LINQ Method Syntax Example: Get all product names in uppercase
Console.WriteLine("\n--- All Product Names (Method Syntax) ---");
var productNamesUppercase = products.Select(p => p.Name.ToUpper()); // Uses a lambda expression for transformation
Console.Write("Names: ");
Console.WriteLine(string.Join(", ", productNamesUppercase));
// 3. Demonstrating Deferred Execution (Conceptual)
Console.WriteLine("\n--- Deferred Execution in Action ---");
var lowCostProductsQuery = products.Where(p => p.Price < 60); // Query definition
Console.WriteLine("Query 'lowCostProductsQuery' defined, but not yet executed.");
// Add a new product to the original list *after* the query definition but *before* execution
products.Add(new Product { Name = "Webcam Pro", Price = 45 });
Console.WriteLine("Added 'Webcam Pro' to the original list.");
Console.WriteLine("Now enumerating 'lowCostProductsQuery':");
foreach (var p in lowCostProductsQuery) // Query execution, including the newly added product
{
Console.WriteLine($"Low Cost Product: {p.Name}, Price: ${p.Price}");
}
}
}
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
Output
--- Original Products ---
Product: Laptop, Price: $1200
Product: Mouse, Price: $25
Product: Keyboard, Price: $75
Product: Monitor, Price: $300
Product: Webcam, Price: $50
Product: Speaker, Price: $150
--- Products over $100 (Query Syntax) ---
Name Price
------------------
Laptop $1200
Monitor $300
Speaker $150
--- All Product Names (Method Syntax) ---
Names: LAPTOP, MOUSE, KEYBOARD, MONITOR, WEBCAM, SPEAKER
--- Deferred Execution in Action ---
Query 'lowCostProductsQuery' defined, but not yet executed.
Added 'Webcam Pro' to the original list.
Now enumerating 'lowCostProductsQuery':
Low Cost Product: Mouse, Price: $25
Low Cost Product: Webcam, Price: $50
Low Cost Product: Webcam Pro, Price: $45
Explanation of the example: A list of Product objects is defined at the beginning of the example.
Query Syntax: The products that cost more than $100 are then sorted by price in descending order using LINQ’s query syntax. These results are projected into an anonymous type that only contains the Name and Price by the select clause.
Method Syntax: To convert all product names to uppercase, it combines a lambda expression with the Select extension method syntax of LINQ.
Deferred Execution: This section provides a conceptual example of how the lowCostProductsQuery is only a definition until it is used repeatedly. The results of the query include the newly added product (“Webcam Pro”) if it is added to the products list after the query is defined but before the foreach loop. The fact that LINQ queries work on the data source during enumeration rather than definition is demonstrated by this.
Consider LINQ to be your data’s all-purpose remote control. LINQ enables you to work with several data sources (databases, XML files, and in-memory lists) using a common C# or VB.NET language syntax, much like a universal remote that enables you to handle multiple electronic devices (TV, DVD player, sound system) with a single interface. Your code will become simpler and easier to comprehend if you tell it what you want (filter, sort, group), and it will figure out how to get it regardless of where the data is stored.
You can also read Generics In C#: Achieving Reusability And Type Safety