Useful LINQ extension methods
Published onEver 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 callingGetEnumerator
or when performing a loop by usingforeach
. This also means that subsequent enumerations will cause the enumerable to recompute, unless we store the results in aList
,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
andThenByDescending
also have overloads that accept aIComparer<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 theEnumerable.Last
andEnumerable.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 theGuid.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.