Obelix
Obelix

Reputation: 708

Casting a generic IEnumerable to a list

I'm splitting a list of (in this example) approx. 190000 items into chunks of 5000 items.

so instead of: List<Object>, count 190000

it becomes: List<List<Object>>, Count 28(Count 5000))

I do this with the following code:

public static IEnumerable<List<Object>> Split(this IEnumerable<Object> sourceList, int chunkSize)
{            
    int numberOfLists = (sourceList.Count() / chunkSize) + 1;
    List<List<Object>> result = new List<List<object>>();

    for (int i = 0; i < numberOfLists; i++)
    {
        List<Object> subList = new List<Object>();
        subList = sourceList.Skip(i * chunkSize).Take(chunkSize).ToList();
        result.Add(subList);
    }

    return result;
}

I call this method (which resides in a helper class) like follows;

var chunkList = (IEnumerable<List<MyObjectClass>>)MyHelper.Split(myObjectList, 5000);

In the above line I explicitly cast the list, that fails in an InvalidCastException. When i use the as operator like follows;

var chunkList = MyHelper.Split(myObjectList, 5000) as IEnumerable<List<MyObjectClass>>;

the result is null.

I expected I could use

List<List<MyObjectClass>> chunkList = MyHelper.Split(myObjectList, 5000) as List<List<MyObjectClass>>

I would like to keep the splitter method as generic as possible. The question is how I can cast the return value correctly. Can someone point out to me how to do this?

Thanks in advance.

Upvotes: 0

Views: 367

Answers (3)

Jon
Jon

Reputation: 437814

As others have stated, the problem is that you are attempting to use the type parameter or List<T> in a variant manner, which is not possible. Instead of this you need to make the splitting method generic so that it has its own type parameter that matches that of the list.

That said, you can turn the method into an iterator block so that it only produces the sub-lists on demand:

public static IEnumerable<List<T>> Partition<T>(this IEnumerable<T> source,
                                                int chunkSize)
{
    while (source.Any())
    {
        yield return source.Take(chunkSize).ToList();
        source = source.Skip(chunkSize);
    }
}

This would be used as

var chunkList = sourceList.Partition(5000);

Note that the above version is free of the off-by-one error that your original code and the solutions based on it all share.

If you don't care about lazy evaluation there is also the possibility of using this trick with GroupBy to do the partitioning:

int i = 0;
var chunkList = sourceList
    .GroupBy(o => i++ / chunkSize) // group into partitions
    .Select(Enumerable.ToList)     // transform each partition into a List
    .ToList()                      // force evaluation of query right now

Upvotes: 2

MarcinJuraszek
MarcinJuraszek

Reputation: 125660

Instead of taking IEnumerable<Object> as parameter and returning IEnumerable<List<Object>> make your method generic:

public static IEnumerable<List<T>> Split<T>(this IEnumerable<T> sourceList, int chunkSize)
{
    int numberOfLists = (sourceList.Count() / chunkSize) + 1;
    var result = new List<List<T>>(numberOfLists);

    for (int i = 0; i < numberOfLists; i++)
    {
        result.Add(sourceList.Skip(i * chunkSize).Take(chunkSize).ToList());
    }

    return result;
}

You can also use Partition method from moreLINQ library. It is more efficient then your solution, because using Skip().Take() causes unnecessary iteration over the same set of elements every time.

Upvotes: 1

Grundy
Grundy

Reputation: 13380

you can use type parameter instead of object

public static IEnumerable<List<T>> Split<T>(this IEnumerable<T> sourceList, int chunkSize)   {
    int numberOfLists = (sourceList.Count() / chunkSize) + 1;
    List<List<T>> result = new List<List<T>>();

    for (int i = 0; i < numberOfLists; i++)
    {
        result.Add(sourceList.Skip(i * chunkSize).Take(chunkSize));
    }

    return result;
}

so for use

IEnumerable<List<MyObjectClass>> chunkList = myObjectClassList.Split(5000);

Upvotes: 2

Related Questions