George Danila's profile picture George Danila's Blog

Useful LINQ extension methods

Published on

Ever since the release of .NET 3.5, the extension methods provided by the System.Linq namespace have helped .NET developers to write more concise and correct code when dealing with collections, and let’s face it, as developers we are constantly dealing with a set of common operations that we perform on collections: filtering, sorting, grouping, projection, computation. Let’s take a look at these operations in detail.

When working with IEnumerable, most of these extension methods are implemented using deferred execution. This means the result is an object that stores the information required to produce a relevant result, at a later time. The actual result is produced when calling GetEnumerator or when performing a loop by using foreach. This also means that subsequent enumerations will cause the enumerable to recompute, unless we store the results in a List, Array or other collection types.

1. Filtering

Enumerable.Where

Very useful when filtering a sequence based on a predicate. Here is a simple example on how to use Where:

// make sure you open the System.Linq namespace in order to access these extension methods  
using System.Linq;
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
numbers.Where(x => x % 2 == 0).ToArray(); // [2, 4, 6]  

Using the Where method is as simple as passing in a lambda expression that conveys what elements to keep in the resulting collection. Note that we’ve used the ToArray extension method in order to project the resulting IEnumerable into a new array. The Where extension method has a useful overload that also provides the index of each element in the source collection along with the actual element.

Enumerable.OfType

Useful when filtering based on a given System.Type:

public abstract class Animal { /*...*/ }
public class Cat : Animal { /*...*/ }
public class Dog : Animal { /*...*/ }
// ...
var animals = new List<Animal>();
animals.Add(new Cat("Garfield"));
animals.Add(new Cat("Mr. Fluffy"));
animals.Add(new Dog("Spike"));
animals.Add(new Dog("Bingo"));

animals.OfType<Dog>(); // only contains Spike and Bingo

This method can also come in handy when working with non-generic collection types, like the System.Collections.ArrayList:

var list = new System.Collections.ArrayList();
list.Add(1);
list.Add("stringValue1");
list.Add("stringValue2");
list.Add(2);
list.Add(3);

list.OfType<string>(); // only contains the 2 string values

Enumerable.Distinct

Returns distinct elements from a given sequence, using the default equality comparer for the provided elements.

var colors = new[] { "red", "green", "blue", "red", "yellow", "blue" };
colors.Distinct().ToArray(); // ["red", "green", "blue", "yellow"]

This method has an overload that accepts a custom IEqualityComparer to be used instead of the default comparer.

Enumerable.Skip

As the name suggests this method will skip the number of elements provided by the count argument:

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
numbers.Skip(5).ToArray(); // [6, 7, 8, 9, 10]

Enumerable.Take

Very similar to Skip, except it includes the first elements provided by the count argument:

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
numbers.Take(3).ToArray(); // [1, 2, 3]

Enumerable.SkipWhile

SkipWhile will skip elements until a certain condition is met:

var numbers = new[] { 1, 2, 3, 4, 5, 6 };
numbers.SkipUntil(x => x > 3).ToArray(); // [4, 5, 6]

Enumerable.TakeWhile

TakeWhile will include elements until a certain condition is met:

var numbers = new[] { 1, 2, 3, 4, 5, 6 };
numbers.TakeWhile(x => x < 4).ToArray(); // [1, 2, 3]

Enumerable.TakeLast

TakeLast takes the last elements from the sequence, based on the provided count argument.

var numbers = new[] { 1, 2, 3, 4, 5, 6 };
numbers.TakeLast(2).ToArray(); // [5, 6]

2. Sorting

Enumerable.OrderBy

OrderBy will produce a sequence sorted in ascending order, using the default comparer. This method requires a key selector for the sorting operation.

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var persons = new[] { 
    new Person { Name = "George", Age = 32 },
    new Person { Name = "John", Age = 20 },
    new Person { Name = "Harry", Age = 31 }
};

persons.OrderBy(x => x.Age).ToArray(); // [ "John", "Harry", "George" ]

Enumerable.OrderByDescending

Very similar to the previous method, except it performs the ordering in descending order.

Enumerable.ThenBy

Can be chained after a OrderBy, OrderByDescending, or other ThenBy calls, to perform a subsequent ordering.

var colors = new[] { "yellow", "green", "red", "teal", "blue", "orange", "violet" };

colors
    .OrderBy(x => x.Length)
    .ThenBy(x => x)
    .ToArray();
// [ "red", "blue", "teal", "green", "orange", "violet", "yellow" ]

The above example orders the colors array by the length of each string first, and then performs a subsequent ordering by passing in the identify function, using the default string comparer, in alphabetical order.

Enumerable.ThenByDescending

Same as ThenBy except it orders the items in descending order, but should be used after a OrderBy or OrderByDescending. Notice the order in which words of the same length appear in:

var colors = new[] { "yellow", "green", "red", "teal", "blue", "orange", "violet" };

colors
    .OrderBy(x => x.Length)
    .ThenByDescending(x => x)
    .ToArray();
// [ "red", "teal", "blue", "green", "yellow", "violet", "orange" ]

OrderBy, ThenBy, OrderByDescending and ThenByDescending also have overloads that accept a IComparer<TKey> used to perform the comparison operation on each key, instead of the default comparer.

3. Grouping

Enumerable.GroupBy

Groups elements of a sequence based on a given key selector function. The result of calling this method is an IEnumerable<IGrouping<TKey, TElement>> where TKey is the type of the key being used to perform the grouping and TElement is the type of the elements in the sequence. Let’s group the above colors array based on the length of each word:

var colors = new[] { "yellow", "green", "red", "teal", "blue", "orange", "violet" };

var grouped = colors.GroupBy(x => x.Length);
foreach (var grouping in grouped)
{
    Console.Write($"Word length {grouping.Key}: ");
    foreach (var color in grouping)
    {
        Console.Write(color + " ");
    }
    Console.WriteLine();
}
// Word length 6: yellow orange violet 
// Word length 5: green 
// Word length 3: red 
// Word length 4: teal blue 

Enumerable.ToLookup

Very similar to GroupBy excepts this creates a one-to-many dictionary of type ILookup that is computed on the spot, instead of a lazy evaluated IEnumerable<IGrouping>.

4. Projection

Enumerable.Select

Applies a projection function to each element of a sequence. Let’s take a look at how to use the Select extension method:

class Person 
{
    public Person(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

var names = new[] { "George", "John", "Harry" };
var people = names.Select(name => new Person(name)).ToArray();
// creates a Person array, by taking each name and creating a Person object with each name

Enumerable.SelectMany

Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into a single sequence.

class Person
{
    public string Name { get; set; }
    public string[] Hobbies { get; set; }
}

var persons = new[] {
    new Person {
        Name = "George", 
        Hobbies = new[] { "reading", "gaming" }
    },
    new Person {
        Name = "John", 
        Hobbies = new[] { "reading", "hiking", "scuba diving" }
    },
    new Person {
        Name = "John", 
        Hobbies = new[] { "gaming", "cooking", "playing guitar" }
    }
};

var hobbies = persons
    .SelectMany(person => person.Hobbies)
    .Distinct() // remove duplicates
    .ToArray(); 
// hobbies = ["reading", "gaming", "hiking", "scuba diving", "cooking", "playing guitar"]

5. Computation

Enumerable.Count

Returns the number of elements in the sequence.

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
var count = numbers.Count();
Console.WriteLine($"There are {count} numbers"); // There are 7 numbers

Enumerable.Any

Returns a value indicating wether at least one element of the sequence satisfies a condition.

var numbers = new[] { 1, 3, -2, 5 };
var containsNegativeNumber = numbers.Any(x => x < 0);
// containsNegativeNumber = True

Enumerable.All

Returns a value indicating wether all of the elements of the sequence satisfy a condition.

var numbers = new[] { 1, 3, -2, 5 };
var allNumbersArePositive = numbers.All(x => x > 0);
// allNumbersArePositive = False

Enumerable.Sum

Returns the sum of a sequence of numeric values.

var numbers = new[] { 1, 2, 3, 4 };
var sum = numbers.Sum();
// sum = 10

Enumerable.Average

Returns the average of a sequence of numeric values.

var numbers = new[] { 2, 4, 6, 8 };
var average = numbers.Average();
// average = 5

Enumerable.Min & Enumerable.Max

As the names suggest, very useful to quickly determine the minimum or maximum value from a sequence of numbers.

Enumerable.SequenceEqual

Checks whether two sequences are equal according to the default equality comparer.

class Person 
{ 
    public string Name { get; set; }
}

var person1 = new Person { Name = "George" };
var person2 = new Person { Name = "John" };
var list = new List<Person> { person1, person2 };
var array = new Person[] { person1, person2 };
var sequencesAreEqual = list.SequenceEqual(array);
// sequencesAreEqual = True

Note that the two sequences don’t have to be of the same type, they just need to contain the same values.

Other standard LINQ extension methods

Enumerable.First & Enumerable.FirstOrDefault

As the name suggests, these methods will return the first element of a sequence. These methods have overloads that can also accept a condition that filters the underlying sequence, so that only elements that satisfy it will be taken into account.

The difference between First and FirstOrDefault is that First will throw an exception if there the sequence contains no elements (or if the condition being passed is not satisfied by any element), where FirstOrDefault will return the default value for the collection type.

var numbers = new[] { 2, 4, 6, 7, 8, 10 };

numbers.Where(x => x % 2 != 0).First(); // produces 7
numbers.First(x => x % 2 != 0); // produces 7
// note: the 2 above lines are functionally equivalent

System.Linq also provides the Enumerable.Last and Enumerable.LastOrDefault extension methods that work in a similar manner to the ones above but return the last element of the sequence.

Enumerable.Single & Enumerable.SingleOrDefault

Similar to the above methods, except these methods will throw an exception if more than one element exists in the underlying sequence (or if more than one element satisfies the filter condition).

Custom extension methods

Shuffle

I found this approach to randomly shuffling a sequence easy to understand, even though it might not be the most efficient implementation:

public static class EnumerableExtensions
{
    public static IEnumerable<T> Shuffle(this IEnumerable<T> source)
    {
        return source?.OrderBy(x => Guid.NewGuid());
    }
}

This implementation makes use of the fact that Guid instances are comparable and that the Guid.NewGuid() method produces random enough values to properly shuffle the sequence.

JoinStrings

I often have to process a sequence of object, use Select to collect a sequence of strings and then have to join these into a single string, with a comma separator. .NET offers the System.String.Join method, but this breaks the flow of the code, since it can’t be called chained together with other System.Linq methods. This is why I’m using a custom JoinStrings extension method:

public static class EnumerableExtensions
{
    public static string JoinStrings(this IEnumerable<string> source, string separator)
    {
        if (source == null) 
            return null;

        return string.Join(separator, source);
    }
}

class Person
{
    public string Name { get; set; }
}

var persons = new[] { 
    new Person { Name = "George" },
    new Person { Name = "John" },
    new Person { Name = "Harry" }
};

var names = persons.Select(x => x.Name).JoinStrings(", ");
// names = "George, John, Harry"

DistinctBy

Sometimes you want to make sure you have a distinct collection based on a certain property - this extension method helps:

public static class EnumerableExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        return items.GroupBy(property).Select(x => x.First());
    }
}

class Person
{    
    public string Name { get; set; }    
}

var persons = new[] {
    new Person { Name = "George" };
    new Person { Name = "John" };
    new Person { Name = "George" };
};

persons.DistincyBy(x => x.Name); // Will only contain the first 2 objects

By grouping on the property and selecting the first element in the series we can ensure we have distinct values for that given property.

MoreLINQ

If you need more useful extension methods you can always check the MoreLINQ project on Github. It’s a great reference for seeing how to implement extension methods that follow the spirit of LINQ.

MoreLINQ contains over 90 extension methods that either complement the standard ones or add missing functionality. Check out the examples documentation for a list of all these methods https://github.com/morelinq/examples.