Exploring LINQ: Deferred Execution, Lambdas, and Extension Methods

Deferred Execution in LINQ

One of the core behaviors of LINQ is deferred execution. When a LINQ query expression is declared, it is not evaluated immediately. Instead, the query variable acts as a blueprint or proxy. The actual iteration and processing of the data only occur when the sequence is accessed, typically within a loop or when a terminating operation is invoked.


var digits = new int[] { 10, 20, 30 };\n\n// Defining the query does not execute it\nvar filteredNumbers = from d in digits\n                      where d > 15\n                      select d;\n\n// Evaluation happens here during iteration\nforeach (var n in filteredNumbers)\n    Console.WriteLine(n);\n

To further illustrate this, consider a custom iterator using yield return. The execution of the extension method will be visibly deferred until the sequence is consumed.


public static class SequenceHelpers\n{\n    public static IEnumerable<string> ToUpperCase(this IEnumerable<string> source)\n    {\n        foreach (string s in source)\n        {\n            Console.WriteLine("Processing: {0}", s);\n            yield return s.ToUpper();\n        }\n    }\n}\n\nclass Program\n{\n    static void Main()\n    {\n        var words = new string[] { "apple", "banana", "cherry" };\n        // Query creation - ToUpperCase is not called yet\n        var result = from w in words.ToUpperCase()\n                     select w;\n\n        // Triggering execution\n        foreach (var r in result)\n            Console.WriteLine("Result: {0}", r);\n    }\n}\n

Because evaluation happens at runtime during iteration, any exceptions related to the data or logic will only surface when the query is accessed, not when it is defined.

Lambda Expressions

Lambda expressions provide a concise way to write anonymous functions. They evolved from the anonymous methods introduced in earlier versions of C#. While not strictly a part of LINQ, they are heavily utilized in LINQ's method-based syntax to define predicates, projections, and key selectors succinctly.

The lambda operator => separates the input parameters on the left from the expression or statement block on the right. For example, (x, y) => x == y specifies two inputs and returns their equality comparison.

If a delegate requires no parameters, empty parentheses are used: () => ExecuteTask(). When the right side contains a single expression, curly braces and the return keyword can be omitted.


// Statement lambda requiring a return\nFunc<int, int, bool> isEqual = (a, b) => { return a == b; };\n\n// Expression lambda\nFunc<int, int, bool> checkEqual = (a, b) => a == b;\n\n// Action with no return type\nAction<string> log = (msg) => Console.WriteLine(msg);\n

The built-in generic delegates Func<T> and Action<T> eliminate the need to define custom delegate types for most scenarios. Func always returns a value, while Action returns void.

Extension Methods

Extension methods allow developers to "add" methods to existing types without creating derived classes or modifying the original source. They are defined as static methods within static classes, with the first parameter prefixed by the this keyword, specifying the type being extended.


static class StringExtensions\n{\n    public static int ConcatenateToInt(this string input, string prefix, string suffix)\n    {\n        return int.Parse(prefix + input + suffix);\n    }\n}\n\nclass Application\n{\n    void Run()\n    {\n        var text = "5";\n        var combined = text.ConcatenateToInt("1", "0"); // Result: 150\n    }\n}\n

The entire LINQ query syntax is underpinned by extension methods defined in the System.Linq namespace. Query expressions are ultimately translated into method chains. For instance, a select clause translates to the Select method, and a where clause translates to Where.


var values = new int[] { 1, 2, 3, 4, 5 };\n\n// Query syntax\nvar querySyntax = from v in values\n                  where v > 2\n                  select v;\n\n// Method syntax using lambdas\nvar methodSyntax = values.Where(v => v > 2);\n

Method syntax allows for chaining operations fluently.


var maxValue = values.Where(v => v > 2).Max();\n

Handling Non-Generic Collections: Cast and OfType

Legacy collections, such as ArrayList or DataTable.Rows, do not implement IEnumerable<T>, preventing direct LINQ usage. The Cast<T> and OfType<T> extension methods bridge this gap by converting IEnumerable to IEnumerable<T>.

The behavior of these two methods differs when encountering incompatible types. Cast<T> attempts to cast every element to the target type; if any element fails, it throws an InvalidCastException. OfType<T>, conversely, skips elements that cannot be cast, yielding only the successful matches without throwing an exception.


var mixedData = new object[] { 10, "text", 20, true };\n\n// Cast will throw an exception because "text" and true cannot be cast to int\nvar castResult = mixedData.Cast<int>();\n\n// OfType will successfully filter out the non-int elements, returning 10 and 20\nvar ofTypeResult = mixedData.OfType<int>();\n

Tags: LINQ C# Deferred Execution Lambda Expressions Extension Methods

Posted on Wed, 28 Oct 2026 16:26:37 +0000 by Kasuke_Akira