Search code examples
c#.netlinqienumerabletake

C# Enumerable.Take with default value


What is the best way to get exactly x values from an Enumerable in C#. If i use Enumerable .Take() like this:

var myList = Enumerable.Range(0,10);
var result = myList.Take(20);

The result will only have 10 elements.

I want to fill the missing entries with a default value. Something like this:

var myList = Enumerable.Range(0,10);
var result = myList.TakeOrDefault(20, default(int));  //Is there anything like this?

Is there such a function in C# and if not, what would be the best way to achieve this?


Solution

  • You could do something like:

    var result = myList.Concat(Enumerable.Repeat(default(int), 20)).Take(20); 
    

    And it would be easy to turn this into an extension method:

    public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, T defaultValue)
    {
        return  list.Concat(Enumerable.Repeat(defaultValue, count)).Take(count);
    }
    

    But there is a subtle gotcha here. This would work perfectly fine for value types, for a reference type, if your defaultValue isn't null, you are adding the same object multiple times. Which probably isn't want you want. For example, if you had this:

    var result = myList.TakeOrDefault(20, new Foo());
    

    You are going to add the same instance of Foo to pad your collection. To solve that problem, you'd need something like this:

    public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, Func<T> defaultFactory)
    {
        return  list.Concat(Enumerable.Range(0, count).Select(i => defaultFactory())).Take(count);
    }
    

    Which you'd call like this:

    var result = myList.TakeOrDefault(20, () => new Foo())
    

    Of course, both methods can co-exist, so you could easily have:

    // pad a list of ints with zeroes
    var intResult = myIntList.TakeOrDefault(20, default(int));
    // pad a list of objects with null
    var objNullResult = myObjList.TakeOrDefault(20, (object)null);
    // pad a list of Foo with new (separate) instances of Foo
    var objPadNewResult = myFooList.TakeOrDefault(20, () => new Foo());