LINQ to Objects
Vaibhav • September 11, 2025
Up to this point, we’ve explored LINQ syntax, filtering, projections, sorting, grouping, joining, and aggregation - all using in-memory collections like lists and arrays. That’s no coincidence. LINQ was originally designed to work with objects in memory, and this form is known as LINQ to Objects. It’s the most common and accessible way to use LINQ, and it’s where most developers begin their journey. In this article, we’ll dive deeper into how LINQ interacts with in-memory collections, what makes it powerful, and how to use it effectively in real-world scenarios.
What Is LINQ to Objects?
LINQ to Objects refers to using LINQ queries on collections that implement IEnumerable<T>
. These include arrays, lists, dictionaries, stacks, queues,
and other generic collections. The queries are executed in-memory, without any external data source like a
database or XML file. This makes LINQ to Objects ideal for everyday programming tasks - filtering, transforming,
and analyzing data that’s already loaded into your application.
LINQ to Objects is part of the System.Linq
namespace. To use it, make sure you include using System.Linq;
at the top
of your file.
Supported Collections
LINQ to Objects works with any collection that implements IEnumerable<T>
.
This includes:
List<T>
T[]
(arrays)Dictionary<K,V>
HashSet<T>
Queue<T>
andStack<T>
These collections are part of the System.Collections.Generic
namespace and are
optimized for performance and flexibility. LINQ adds a layer of expressive querying on top of them.
Basic Query Example
Let’s start with a simple example using a list of integers. We’ll filter even numbers and project their squares.
List numbers = new List { 1, 2, 3, 4, 5, 6 };
var squares = numbers
.Where(n => n % 2 == 0)
.Select(n => n * n);
This query uses method syntax to filter even numbers and then project their squares. The result is a deferred query - it won’t execute until you enumerate it.
Query Syntax with Objects
Query syntax is also supported with LINQ to Objects. Here’s an example using a list of custom objects:
class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
List students = new List
{
new Student { Name = "Alice", Score = 85 },
new Student { Name = "Bob", Score = 92 },
new Student { Name = "Charlie", Score = 78 }
};
var highScorers = from s in students
where s.Score > 80
select s.Name;
This query selects the names of students who scored above 80. It’s readable and declarative - ideal for scenarios where clarity matters.
Combining LINQ with Lists
Lists are the most commonly used collection with LINQ. They support dynamic resizing, indexing, and fast iteration. LINQ adds powerful querying capabilities on top.
List fruits = new List { "apple", "banana", "cherry", "apricot" };
var aFruits = fruits
.Where(f => f.StartsWith("a"))
.OrderBy(f => f);
This query filters fruits that start with “a” and sorts them alphabetically. The result is a deferred sequence that can be enumerated or materialized.
Working with Arrays
Arrays are fixed-size collections, but LINQ treats them just like lists. You can apply the same queries without any changes.
int[] data = { 3, 1, 4, 1, 5, 9 };
var sorted = data
.Distinct()
.OrderBy(n => n);
This query removes duplicates and sorts the array. The result is a new sequence with unique, ordered elements.
LINQ with Dictionaries
Dictionaries store key-value pairs. LINQ can query both keys and values, but you need to work with KeyValuePair<K,V>
objects.
Dictionary scores = new Dictionary
{
{ "Alice", 85 },
{ "Bob", 92 },
{ "Charlie", 78 }
};
var topScores = scores
.Where(kvp => kvp.Value > 80)
.Select(kvp => kvp.Key);
This query selects the names of students with scores above 80. The kvp
variable
represents each key-value pair.
Materializing Results
LINQ queries are deferred by default. To store the results, you can materialize them using ToList()
or ToArray()
.
var result = highScorers.ToList();
This executes the query and stores the results in a list. Materialization is useful when you need to reuse the results or avoid repeated execution.
LINQ and Performance
LINQ to Objects is efficient for small to medium-sized collections. It uses optimized algorithms and short-circuiting where possible. However, for large datasets or performance-critical code, you should measure and profile your queries.
Avoid chaining too many LINQ operations on large collections. Materialize
intermediate results if needed, and use foreach
for performance-sensitive
loops.
LINQ and Immutability
LINQ encourages a functional style of programming. Queries don’t modify the original collection - they return new sequences. This makes your code safer and easier to reason about.
var doubled = numbers.Select(n => n * 2); // Original list unchanged
The numbers
list remains unchanged. The doubled
sequence contains the transformed values.
LINQ and Readability
One of the biggest advantages of LINQ to Objects is readability. Queries express intent clearly - filtering, sorting, projecting - without boilerplate code.
var expensive = products.Where(p => p.Price > 800);
This reads like a sentence: “from products, select those where price is greater than 800.” It’s concise and expressive.
Using LINQ with Custom Comparers
Some LINQ methods accept custom comparers. For example, Distinct
, Union
, and GroupBy
can use IEqualityComparer<T>
to define equality logic.
var uniqueByName = products.DistinctBy(p => p.Name); // .NET 6+
This removes duplicates based on product name. It’s a powerful way to customize behavior without modifying the class.
LINQ and Debugging
Because LINQ queries are deferred, debugging can be tricky. Use ToList()
to
force execution and inspect results.
var debugView = query.ToList(); // Forces execution
This helps you understand what the query is doing and catch errors early.
Summary
LINQ to Objects is the foundation of LINQ in C#. It allows you to query in-memory collections using expressive, readable syntax. Whether you’re working with lists, arrays, dictionaries, or other generic collections, LINQ provides powerful tools for filtering, projecting, sorting, grouping, and aggregating data. Queries are deferred by default, executed only when needed, and return new sequences without modifying the original data. By mastering LINQ to Objects, you’ll write cleaner, safer, and more maintainable code - and unlock the full potential of C#’s functional capabilities.
In the next article, we’ll explore LINQ Performance - how to write efficient queries, avoid common pitfalls, and optimize LINQ for real-world applications.