6 useful extensions for IEnumerable

5/23/2023

I did already write about some useful extension methods for Task and ValueTask. Today I want to show you some useful extension methods for IEnumerable.

Disclaimer

Those methods are extension methods for IEnumerable. Be careful if your underlying type is something like IQueryable because we might materialize the query here. Instead of IEnumerable you could use IReadOnlyCollection so you know that you are not dealing with a queryable and your collection at hand is already materialized.

IsNullOrEmpty

Just like string, we can introduce a IsNullOrEmpty extension method for IEnumerable:

public static bool IsNullOrEmpty<T>(this IEnumerable<T>? source) => source is null || !source.Any();

var isEmpty = Enumerable.Empty<int>().IsNullOrEmpty(); // true
var isEmpty = new[] { 1, 2, 3 }.IsNullOrEmpty(); // false
var isEmpty = ((IEnumerable<int>)null).IsNullOrEmpty(); // true

Partitioning

Partitioning is a common operation on collections. We can partition a collection into two collections. One collection contains all elements that match a predicate and the other collection contains all elements that do not match the predicate.

public static (IEnumerable<T> True, IEnumerable<T> False) Partition<T>(
        this IEnumerable<T> source,
        Func<T, bool> predicate)
    {
        ArgumentNullException.ThrowIfNull(source);
        ArgumentNullException.ThrowIfNull(predicate);

        var trueItems = new List<T>();
        var falseItems = new List<T>();

        foreach (var item in source)
        {
            if (predicate(item))
                trueItems.Add(item);
            else
                falseItems.Add(item);
        }

        return (trueItems, falseItems);
    }
}

The usage would be like this:

var (even, odd) = numbers.Partition(n => n % 2 == 0);
Console.WriteLine($"Even numbers: {string.Join(", ", even)}");
Console.WriteLine($"Odd numbers: {string.Join(", ", odd)}");

Output:

Even numbers: 0, 2, 4
Odd numbers: 1, 3

Median

The median is the middle number in a sorted, ascending or descending, list of numbers and can be more descriptive of that data set than the average. LINQ itself ships the Average extension method but not the Median extension method. We can easily add it:

public static double Median<T>(this IEnumerable<T> source) where T : IConvertible
{
    ArgumentNullException.ThrowIfNull(source);

    var sortedList = source.Select(x => x.ToDouble(CultureInfo.InvariantCulture)).OrderBy(x => x).ToList();
    var count = sortedList.Count;

    if (count == 0)
    {
        throw new InvalidOperationException("The source sequence is empty.");
    }

    if (count % 2 == 0)
    {
        return (sortedList[count / 2 - 1] + sortedList[count / 2]) / 2;
    }

    return sortedList[count / 2];
}

Usage:

var median = new[] { 1, 1, 1, 1, 5, 6, 7, 8, 9 }.Median(); // 5
var average = new[] { 1, 1, 1, 1, 5, 6, 7, 8, 9 }.Average(); // 4.333333333333333

Mode

The mode is the number that is repeated most often in a set of numbers. We can easily add it:

public static IEnumerable<T> Mode<T>(this IEnumerable<T> source)
{
    ArgumentNullException.ThrowIfNull(source);

    var groups = source.GroupBy(x => x);
    var maxCount = groups.Max(g => g.Count());
    return groups.Where(g => g.Count() == maxCount).Select(g => g.Key);
}

Usage:

var mode = new[] { 1, 1, 1, 1, 5, 6, 7, 8, 9 }.Mode(); // 1

If two numbers are repeated the same amount of times, we get both numbers!

StandardDeviation

The standard deviation is a measure of how spread out numbers are.

public static double StandardDeviation<T>(this IEnumerable<T> source) where T : IConvertible
{
    ArgumentNullException.ThrowIfNull(source);

    var values = source.Select(x => x.ToDouble(CultureInfo.InvariantCulture)).ToList();
    var count = values.Count;

    if (count == 0)
    {
        throw new InvalidOperationException("The source sequence is empty.");
    }

    var avg = values.Average();
    var sum = values.Sum(d => Math.Pow(d - avg, 2));
    return Math.Sqrt(sum / count);
}

Usage:

var standardDeviation = new[] { 1, 1, 1, 1, 5, 6, 7, 8, 9 }.StandardDeviation(); // 3.1622776601683795

Shuffle

This method shuffles the elements in a sequence using Fisher-Yates shuffle algorithm.

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
    ArgumentNullException.ThrowIfNull(source);

    var elements = source.ToArray();
    var random = Random.Shared;
    for (var i = elements.Length - 1; i > 0; i--)
    {
        var swapIndex = random.Next(i + 1);
        (elements[i], elements[swapIndex]) = (elements[swapIndex], elements[i]);
    }

    return elements;
}

Usage:

var numbers = Enumerable.Range(0, 5);
var random = numbers.Shuffle();

// 4, 0, 1, 3, 2
Console.WriteLine(string.Join(", ", random));

Conclusion

Today we saw some useful extension methods for IEnumerable. I hope you find them useful. If you have any questions or feedback feel free to leave a comment below.

Task vs ValueTask - The what's, when's and how's

Since .NET Core 2.0 the Type ValueTask. It seems that there is a lot of overlap between Task and ValueTask. So let's have a deeper look into ValueTask. Where should we use it and how should we use it properly? And also: where we should not use is.

IEnumerable vs IQueryable - What's the difference

.NET brings two types which seem very similiar

  • IEnumerable
  • IQueryable

What is the difference? Most are familiar with using IQueryable when we want to go to the database and back. But why not using IEnumerable?

5 useful extensions for Task<T> in .NET

In this short blog post, I will show you 5 useful extensions for Task in .NET. We will build them as extension methods, so there are easy to use. On top, I will show a small example of how to use them. So let's go!

An error has occurred. This application may no longer respond until reloaded. Reload x