Writing LINQ Queries Over Arrays and Lists in C#

Query, filter, and transform in-memory data the modern way

When working with collections in C#, developers often rely on loops to find, filter, and manipulate data. But there’s a faster, more expressive approach — LINQ to Objects, which allows you to query arrays and lists as if they were databases.

Here we’ll explore how to write LINQ queries over arrays and lists, demonstrating practical examples, syntax patterns, and best practices for clean and efficient C# code.


🔹 What is LINQ to Objects?

LINQ to Objects allows you to query any in-memory data structure that implements IEnumerable<T> — including arrays, lists, and other generic collections.

Instead of writing repetitive for or foreach loops, you can use declarative queries to describe what you want, not how to get it.

Example:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

var evens = from n in numbers
            where n % 2 == 0
            select n;

foreach (var n in evens)
    Console.WriteLine(n);

✅ Output:

2
4
6
8

The from…where…select syntax gives you clean, readable, SQL-like control over arrays and lists.


💡 Arrays vs Lists in LINQ

Both arrays (T[]) and lists (List<T>) implement IEnumerable<T>, so they can be queried in exactly the same way.

The difference lies in mutability and performance characteristics, not in how LINQ operates.

Collection TypeDescriptionExample
ArrayFixed size, fast accessint[] numbers = {1,2,3};
ListDynamic size, flexible methodsList<int> nums = new() {1,2,3};

Both can be filtered, projected, grouped, or ordered using LINQ.


⚙️ Filtering Data with LINQ

The most common operation is filtering — selecting only the items that meet a condition.

List<string> names = new List<string> 
{ 
    "Alice", "Bob", "Charlie", "David", "Eve" 
};

var shortNames = from name in names
                 where name.Length <= 4
                 select name;

foreach (var n in shortNames)
    Console.WriteLine(n);

✅ Output:

Bob
Eve

Equivalent method syntax:

var shortNames = names.Where(name => name.Length <= 4);

🔁 Ordering Data

Use orderby (query syntax) or OrderBy() / OrderByDescending() (method syntax) to sort data.

int[] scores = { 70, 85, 90, 60, 95 };

var sortedScores = from s in scores
                   orderby s descending
                   select s;

foreach (var s in sortedScores)
    Console.WriteLine(s);

✅ Output:

95
90
85
70
60

🧩 Projecting Data with select

The select keyword lets you transform items as you query them — known as projection.

string[] fruits = { "apple", "banana", "cherry" };

var upper = from f in fruits
            select f.ToUpper();

foreach (var f in upper)
    Console.WriteLine(f);

✅ Output:

APPLE
BANANA
CHERRY

You can also create anonymous types:

var fruitInfo = from f in fruits
                select new { Original = f, Length = f.Length };

foreach (var info in fruitInfo)
    Console.WriteLine($"{info.Original} ({info.Length})");

✅ Output:

apple (5)
banana (6)
cherry (6)

🧠 Combining Filters and Projections

You can chain multiple LINQ operations together for powerful results.

List<int> numbers = new() { 5, 8, 13, 21, 34, 55, 89 };

var result = from n in numbers
             where n > 10 && n % 2 == 1
             orderby n descending
             select n * 2;

foreach (var r in result)
    Console.WriteLine(r);

✅ Output:

178
110
26

This example:

  • Filters numbers greater than 10
  • Keeps only odd numbers
  • Orders them descending
  • Doubles each value

All in just one expressive query.


🧮 Aggregating Data

LINQ also supports aggregation — counting, summing, or averaging elements.

int[] ages = { 18, 21, 25, 30, 40 };

Console.WriteLine(ages.Count());   // 5
Console.WriteLine(ages.Average()); // 26.8
Console.WriteLine(ages.Max());     // 40
Console.WriteLine(ages.Min());     // 18
Console.WriteLine(ages.Sum());     // 134

These methods come from the System.Linq namespace and work on any IEnumerable<T>.


🚀 Real-World Example: Working with Objects

LINQ shines when working with lists of complex objects.

class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
}

var students = new List<Student>
{
    new() { Name = "Alice", Grade = 85 },
    new() { Name = "Bob", Grade = 92 },
    new() { Name = "Charlie", Grade = 78 }
};

var topStudents = from s in students
                  where s.Grade >= 80
                  orderby s.Grade descending
                  select new { s.Name, s.Grade };

foreach (var s in topStudents)
    Console.WriteLine($"{s.Name} - {s.Grade}");

✅ Output:

Bob - 92
Alice - 85

⚡ LINQ Method Syntax Examples

If you prefer method chaining, the same query can be written as:

var topStudents = students
    .Where(s => s.Grade >= 80)
    .OrderByDescending(s => s.Grade)
    .Select(s => new { s.Name, s.Grade });

Both produce identical results — it’s purely a matter of coding style and context.


🧪 Challenge Task

Write a program that:

  • Creates a list of cities with population data
  • Filters only cities above 100,000 residents
  • Orders them by name
  • Projects results as anonymous types with name and population formatted nicely

Try it using both query syntax and method syntax.


📚 Summary

OperationDescriptionExample
Where()Filter datanumbers.Where(n => n > 5)
Select()Transform datanames.Select(n => n.ToUpper())
OrderBy()Sort ascendingscores.OrderBy(s => s)
OrderByDescending()Sort descendingscores.OrderByDescending(s => s)
Count() / Sum() / Average()Aggregateages.Average()
ToList()Force executionquery.ToList()

✅ Best Practices

  • Always using System.Linq; at the top of your file
  • Prefer method syntax when chaining many operations
  • Use deferred execution wisely — queries run when enumerated
  • Keep queries readable; break into steps if they get long
  • Avoid modifying the collection while it’s being queried

🧑‍💻 Want more?
Join our Coding Workshops – learn to combine filtering, grouping, and joining collections with real-world datasets and performance insights for modern .NET applications.