← Open in the full interactive course (progress tracking, search & more)

You've been writing LINQ to Objects since Intermediate Part IV. This lesson just gives it its name — and the family it belongs to.

Quick question: what is "LINQ," exactly? If your answer is "Where, Select, OrderBy, that kind of thing" — you're not wrong, but you're only seeing one piece of something bigger. "LINQ" was never the name of a single technology. It's the name of a pattern: one consistent query syntax and method syntax, reused across several completely different execution engines, each called a provider. Every LINQ query you've ever written — from Intermediate's first Where lambda, through this Part's deep dive into IEnumerable<T> and IQueryable<T> — was really a query against one specific provider, and until now, that provider mostly didn't need a name.

This lesson gives it one: LINQ to Objects — LINQ operators run against any in-memory IEnumerable<T>, executed as pure, compiled C# delegates, with no expression tree involved unless you explicitly opt in. You'll see exactly how it fits alongside LINQ to Entities (EF Core, next lesson) and the historical names — LINQ to XML, LINQ to SQL — you'll encounter in the wild.

What Is It?

The Simple Explanation

LINQ (Language-Integrated Query) is a single, unified syntax for querying — filtering, sorting, projecting, grouping — that C# lets you write consistently, no matter what you're querying. LINQ to Objects is what you get when the thing being queried is a plain in-memory collection: an array, a List<T>, a Dictionary<TKey,TValue>, or any custom type implementing IEnumerable<T> (including one you wrote yourself with yield return, from earlier in this Part). It's the provider you reach for the moment your data already lives in your application's own memory.

The Technical Definition

LINQ to Objects is the set of extension methods defined on IEnumerable<T> by the System.Linq.Enumerable static class — Where, Select, OrderBy, GroupBy, and every other operator you've used throughout this course. Each of those methods accepts a plain Func<T,...> delegate, not an Expression<TDelegate> — meaning the C# compiler turns your lambda straight into executable IL, exactly like any other method call, with no intermediate data structure describing the query. There is nothing to "translate," because the code that runs is the query.

List<int> numbers = [1, 2, 3, 4, 5, 6]; // This is LINQ to Objects: numbers is IEnumerable<int>, the lambda // compiles straight to a Func<int, bool> delegate, and Where(...) // runs that delegate directly against each in-memory item. var evens = numbers.Where(n => n % 2 == 0);

Why Does It Exist?

The Problem — One Query Language, Many Data Sources, No Shared Syntax

Before LINQ existed (C# 2.0 and earlier), querying an in-memory list meant hand-written loops. Querying a database meant raw SQL strings and a completely different API (ADO.NET's SqlCommand, readers, and so on). Querying XML meant yet another API, with its own navigation methods. Three different data sources, three unrelated skill sets, none of which transferred to the others — a developer moving from filtering a list to filtering a database table had to reach for an entirely different toolbox.

The Solution — One Query Syntax, Many Interchangeable Providers

LINQ's actual insight was making the query syntax itself reusable, while letting each data source supply its own provider — the engine that actually executes the query, in whatever way makes sense for that data source. Write .Where(x => x.Price > 20) against a List<Product>, and LINQ to Objects runs it as a delegate, in memory. Write the exact same-looking .Where(x => x.Price > 20) against context.Products, and LINQ to Entities (the next lesson) turns it into a SQL WHERE clause instead. Same syntax you already know either way — the provider decides what happens underneath.

Big Picture

LINQ is the umbrella name. Underneath it sit several providers, each targeting a different kind of data source:

ProviderTargetsExecutes asStatus
LINQ to ObjectsAny in-memory IEnumerable<T> — arrays, List<T>, custom iteratorsCompiled C# delegates (Func<T,...>), no expression treeWhat this lesson covers — and what you've been doing all along
LINQ to EntitiesAn EF Core DbSet<T> / IQueryable<T>, backed by a real databaseAn Expression tree, translated into SQL by EF Core's providerCovered in depth in the next lesson
LINQ to XMLXDocument / XElement treesIn-memory traversal over a loaded XML documentStill available, real, and occasionally seen in the wild — not taught in depth here
LINQ to SQLSQL Server specifically, via its own separate O/RMTranslated to SQL, much like LINQ to EntitiesLegacy — effectively superseded by EF Core; recognize the name in older code, don't reach for it in new projects

Every one of these shares the same Where/Select/OrderBy vocabulary and the same query-syntax option (from x in source select x) — that consistency is the entire point of calling it all "LINQ." What differs, provider to provider, is what happens the moment you ask for results.

How It Works

HOW THE COMPILER DECIDES WHICH PROVIDER YOUR QUERY USES
1. THE COMPILER LOOKS AT THE STATIC TYPE OF WHAT YOU'RE QUERYING
2. IF THAT TYPE IS IEnumerable<T> (AND NOT IQueryable<T>) — LINQ TO OBJECTS
3. IF THAT TYPE IS IQueryable<T> — A DIFFERENT PROVIDER TAKES OVER
4. THIS DECISION HAPPENS PER CALL, AT COMPILE TIME — NOT ONCE, GLOBALLY

Simple Example

This is nothing new mechanically — it's every LINQ query you've written since Intermediate's Filtering lesson. What's new is naming what's actually happening:

public record Product(int Id, string Name, decimal Price, int Stock); List<Product> products = [ new(1, "Wireless Mouse", 24.99m, 40), new(2, "Standing Desk", 349.00m, 0), new(3, "Desk Lamp", 19.50m, 15), ]; // LINQ to Objects, start to finish: // - 'products' is List<Product>, which implements IEnumerable<Product> // - Where(...) and OrderBy(...) resolve to System.Linq.Enumerable // - each lambda compiles to a Func<Product, ...> delegate — ordinary IL // - execution happens entirely inside THIS process, over the List<T> already in memory var affordable = products .Where(p => p.Price < 50 && p.Stock > 0) .OrderBy(p => p.Price); foreach (var p in affordable) Console.WriteLine($"{p.Name}: {p.Price:C}"); // Desk Lamp: $19.50 // Wireless Mouse: $24.99

Code → Meaning → Result: Nothing about running this code involves a database, a network call, or a translation step of any kind — products was already sitting in memory before the query even started, and it stays there the whole time. That's the entire definition of LINQ to Objects: the provider is "your own process, executing compiled delegates."

Query Syntax Is Still LINQ to Objects, Too

// Query syntax — compiles down to the exact same Enumerable method calls var affordable = from p in products where p.Price < 50 && p.Stock > 0 orderby p.Price select p;

The C# compiler rewrites query syntax into the equivalent method-syntax calls before anything else happens — so this is the identical LINQ to Objects query as above, just spelled differently. The provider is determined by the source's type (IEnumerable<T> vs. IQueryable<T>), never by which syntax you choose to write.

Real-World Example

A background service polls a payment gateway, gets back a batch of transactions as a plain List<Transaction> (already fully in memory — it came from a JSON HTTP response, not a database), and needs to summarize suspicious activity before writing an alert:

public record Transaction(int AccountId, decimal Amount, string Country, DateTime OccurredAt); public IEnumerable<Transaction> FindSuspicious(List<Transaction> batch) { // batch is already in memory — this is LINQ to Objects end to end. // There's no database connection here to translate anything into. return batch .Where(t => t.Amount > 5000m) .Where(t => t.Country != "US") .OrderByDescending(t => t.Amount); }

Contrast this with the very next lesson's example, where the near-identical filter runs against context.Transactions instead — same-looking C# code, but this time IQueryable<T> and a SQL translation step are involved. Recognizing which one you're looking at — by checking whether the source is an in-memory collection or a DbSet<T> — is exactly the skill this lesson is building.

Analogy

One Language, Spoken to Different Audiences

Imagine one universal phrase — "please sort these by date" — that you can say to a librarian standing in front of a shelf of books, or phone to a records office across town. Said to the librarian standing right there, they just pick up each book and physically reorder them on the shelf, right now, themselves — that's LINQ to Objects: the work happens right where the data already is, using the simplest, most direct mechanism available. Said over the phone to the records office, your words get relayed, reinterpreted into their internal filing procedure, and carried out by their own staff, in their own building, using their own system — that's LINQ to Entities: the same phrase, but translated and executed somewhere else entirely, by a different set of rules.

The phrase never changes. What changes is who's listening, and what they do with it.

Under the Hood

Func<T,...> vs Expression<TDelegate> — THE ACTUAL FORK IN THE ROAD
1. Enumerable's METHODS TAKE Func<T,...> PARAMETERS
2. Queryable's METHODS TAKE Expression<Func<T,...>> PARAMETERS INSTEAD
3. THE COMPILER CHOOSES BASED ON OVERLOAD RESOLUTION, USING THE SOURCE'S STATIC TYPE
4. LINQ TO OBJECTS NEVER BUILDS AN EXPRESSION TREE UNLESS YOU EXPLICITLY OPT IN

Common Confusion

1. "LINQ to Objects, LINQ to Entities, LINQ to SQL — these are the same thing with different names"

They share syntax, not execution. LINQ to Objects runs compiled C# in your own process. LINQ to Entities and LINQ to SQL both translate an expression tree into SQL and run it on a database server — an entirely different machine, in an entirely different language, with an entirely different set of what's even expressible (the next two lessons cover exactly that boundary). The name "LINQ" describes the shared query vocabulary — it says nothing about where or how the query actually executes.

2. "I've never explicitly chosen LINQ to Objects, so maybe I've never used it"

Nearly every LINQ query in Intermediate — filtering a List<Employee>, grouping a report by category, projecting a collection of orders into DTOs — was LINQ to Objects, whether or not the name came up. The provider was decided the moment you queried a plain IEnumerable<T>; you just hadn't been given the formal vocabulary for it yet. This lesson doesn't teach a new skill — it names a skill you already have.

Common Mistakes

Mistake 1 — Assuming a query "is LINQ to Entities" just because the source came from a database originally

Believing context.Products.ToList().Where(...) is still LINQ to Entities because the data started life in a database. Once .ToList() runs, the result is a plain List<Product> — every operator chained after it is LINQ to Objects, executed entirely in memory, with no further translation happening. Track the provider by the current static type at each point in the chain, not by where the data originally came from.

Mistake 2 — Treating "LINQ to SQL" and "LINQ to Entities" as interchangeable names for EF Core

Calling EF Core "LINQ to SQL" in conversation or documentation — they're historically distinct technologies: LINQ to SQL was Microsoft's earlier, SQL-Server-only O/RM, now legacy; LINQ to Entities is EF Core's LINQ provider, actively developed, and provider-agnostic (SQL Server, PostgreSQL, SQLite, and others). Use "LINQ to Entities" (or simply "EF Core's LINQ provider") for current work — recognize "LINQ to SQL" only as a name you might see in an older codebase or article.

When Should I Use It?

LINQ to Objects is the right lens when

Reach for a different provider when

Rule of thumb: Ask "is this source already sitting in my process's memory as an IEnumerable<T>, or is it an IQueryable<T> still waiting to be translated and sent elsewhere?" The answer names the provider — and tells you exactly what kind of execution model you're actually working with.

Mental Model

LINQ = one query vocabulary, many providers.
LINQ to Objects = the provider for in-memory IEnumerable<T> — compiled delegates, no translation, runs in your own process.
LINQ to Entities = the provider for EF Core's IQueryable<T> — expression trees, translated to SQL, runs on the database.

Remember: the syntax never tells you the provider — the source's static type does.

Key Takeaway


Check Your Understanding

You've just given a name to something you've been doing since Intermediate Part IV. Let's confirm the organizing structure stuck.

1. What is the most accurate way to describe what "LINQ" refers to?

Show answer

Correct: B

Why B is correct: As covered in What Is It? and Big Picture, LINQ is the umbrella name for one consistent query syntax that multiple providers — LINQ to Objects, LINQ to Entities, LINQ to XML, and others — each implement with their own execution model underneath.

Why A is incorrect: LINQ predates and extends beyond database querying entirely — LINQ to Objects, the subject of this lesson, has nothing to do with databases.

Why C is incorrect: EF Core is one specific implementation of one specific LINQ provider (LINQ to Entities) — it isn't a synonym for LINQ as a whole.

Why D is incorrect: In-memory collections are exactly what LINQ to Objects targets, but that's only one provider among several — LINQ itself is broader than any single one of them.

Reinforcement: Think "LINQ" the pattern, "LINQ to X" the specific provider that actually executes a given query.

2. A developer writes someList.Where(x => x.IsActive), where someList is a List<Customer>. What determines that this is LINQ to Objects, rather than LINQ to Entities?

Show answer

Correct: B

Why B is correct: As explained in Under the Hood, the deciding factor is the static type of the source — List<T> only implements IEnumerable<T>, so overload resolution picks Enumerable.Where, which takes a plain delegate, making this LINQ to Objects by construction.

Why A is incorrect: The shape of the lambda's body has no bearing on which provider handles the query — the same lambda syntax works identically for either provider; what differs is how it's compiled underneath.

Why C is incorrect: async/await is unrelated to which LINQ provider a query uses — LINQ to Objects and LINQ to Entities both work fine in synchronous or asynchronous code.

Why D is incorrect: This is exactly the mistake called out in Common Mistakes — once data is materialized into a plain List<T>, its original source is irrelevant; only its current static type decides the provider.

Reinforcement: Always check the current static type of the LINQ source at each step — that alone tells you which provider is in play.

3. Which statement correctly distinguishes LINQ to Objects from LINQ to Entities in terms of what the compiler generates for a lambda passed to Where(...)?

Show answer

Correct: A

Why A is correct: As detailed in Under the Hood, this is the actual fork in the road — Enumerable's methods take Func<T,...> parameters (compiled, runnable delegates), while Queryable's methods take Expression<Func<T,...>> parameters (a tree of objects describing the lambda's structure, inspectable and translatable).

Why B is incorrect: The compiled output genuinely differs at compile time, based on overload resolution — it's not a runtime distinction that only shows up later.

Why C is incorrect: Neither provider has any inherent relationship to async/await — that's an unrelated language feature.

Why D is incorrect: This reverses the actual mapping — it's LINQ to Entities (via IQueryable<T>) that uses expression trees, and LINQ to Objects (via IEnumerable<T>) that uses plain delegates.

Reinforcement: Func<T,...> = code that runs. Expression<Func<T,...>> = data describing code, for something else to interpret and translate.

4. Which of these is still a real, currently-used LINQ provider, as opposed to a legacy name mainly worth recognizing in older code?

Show answer

Correct: B

Why B is correct: As covered in Big Picture, LINQ to XML remains available and occasionally used for querying XDocument/XElement trees — it isn't taught in depth in this course, but it's a live, current part of .NET, unlike LINQ to SQL.

Why A is incorrect: LINQ to SQL is specifically called out as legacy — effectively superseded by EF Core (LINQ to Entities) for new development, though the name still appears in older codebases and articles.

Why C is incorrect: LINQ to XML is not described as legacy in this lesson — only LINQ to SQL is.

Why D is incorrect: The Big Picture table lists four provider names explicitly, including LINQ to XML and LINQ to SQL — the ecosystem is broader than just the two providers this course covers in depth.

Reinforcement: Recognizing a provider's name and status (current vs. legacy) is useful even without studying it in depth — it helps you correctly interpret code and documentation you encounter later.

You now have the formal vocabulary for something you've been practicing all along. Next up: a genuine deep dive into the provider you'll use most in production — LINQ to Entities, and exactly how EF Core turns your query into SQL.


dotnetmadeeasy.com — Learn C# and .NET, the right way.