Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast from IEnumerable to IEnumerable<object>

I prefer to use IEnumerable<object>, for LINQ extension methods are defined on it, not IEnumerable, so that I can use, for example, range.Skip(2). However, I also prefer to use IEnumerable, for T[] is implicitly convertible to IEnumerable whether T is a reference type or value type. For the latter case, no boxing is involved, which is good. As a result, I can do IEnumerable range = new[] { 1, 2, 3 }. It seems impossible to combine the best of both worlds. Anyway, I chose to settle down to IEnumerable and do some kind of cast when I need to apply LINQ methods.

From this SO thread, I come to know that range.Cast<object>() is able to do the job. But it incurs performance overhead which is unnecessary in my opinion. I tried to perform a direct compile-time cast like (IEnumerable<object>)range. According to my tests, it works for reference element type but not for value type. Any ideas?

FYI, the question stems from this GitHub issue. And the test code I used is as follows:

static void Main(string[] args)
{
    // IEnumerable range = new[] { 1, 2, 3 }; // won't work
    IEnumerable range = new[] { "a", "b", "c" };
    var range2 = (IEnumerable<object>)range;
    foreach (var item in range2)
    {
        Console.WriteLine(item);
    }
}
like image 738
Lingxi Avatar asked Jul 19 '16 07:07

Lingxi


3 Answers

According to my tests, it works for reference element type but not for value type.

Correct. This is because IEnumerable<out T> is co-variant, and co-variance/contra-variance is not supported for value types.

I come to know that range.Cast() is able to do the job. But it incurs performance overhead which is unnecessary in my opinion.

IMO the performance cost(brought by boxing) is unavoidable if you want a collection of objects with a collection of value-types given. Using the non-generic IEnumerable won't avoid boxing because IEnumerable.GetEnumerator provides a IEnumerator whose .Current property returns an object. I'd prefer always use IEnumerable<T> instead of IEnumerable. So just use the .Cast method and forget the boxing.

like image 59
Cheng Chen Avatar answered Oct 31 '22 13:10

Cheng Chen


After decompiling that extension, the source showed this:

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
    {
      IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
      if (enumerable != null)
        return enumerable;
      if (source == null)
        throw Error.ArgumentNull("source");
      return Enumerable.CastIterator<TResult>(source);
    }

    private static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
    {
      foreach (TResult result in source)
        yield return result;
    }

This basically does nothing else than IEnumerable<object> in first place.

You stated:

According to my tests, it works for reference element type but not for value type.

How did you test that?

like image 2
lokusking Avatar answered Oct 31 '22 12:10

lokusking


Despite I really do not like this approach, I know it is possible to provide a toolset similar to LINQ-to-Objects that is callable directly on an IEnumerable interface, without forcing a cast to IEnumerable<object> (bad: possible boxing!) and without casting to IEnumerable<TFoo> (even worse: we'd need to know and write TFoo!).

However, it is:

  • not free for runtime: it may be heavy, I didn't run perfomance test
  • not free for developer: you actually need to write all those LINQ-like extension methods for IEnumerable (or find a lib that does it)
  • not simple: you need to inspect the incoming type carefully and need to be careful with many possible options
  • is not an oracle: given a collection that implements IEnumerable but does not implement IEnumerable<T> it only can throw error or silently cast it to IEnumerable<object>
  • will not always work: given a collection that implements both IEnumerable<int> and IEnumerable<string> it simply cannot know what to do; even giving up and casting to IEnumerable<object> doesn't sound right here

Here's an example for .Net4+:

using System;
using System.Linq;
using System.Collections.Generic;

class Program
{
    public static void Main()
    {
        Console.WriteLine("List<int>");
        new List<int> { 1, 2, 3 }
            .DoSomething()
            .DoSomething();

        Console.WriteLine("List<string>");
        new List<string> { "a", "b", "c" }
            .DoSomething()
            .DoSomething();

        Console.WriteLine("int[]");
        new int[] { 1, 2, 3 }
            .DoSomething()
            .DoSomething();

        Console.WriteLine("string[]");
        new string[] { "a", "b", "c" }
            .DoSomething()
            .DoSomething();

        Console.WriteLine("nongeneric collection with ints");
        var stack = new System.Collections.Stack();
        stack.Push(1);
        stack.Push(2);
        stack.Push(3);
        stack
            .DoSomething()
            .DoSomething();

        Console.WriteLine("nongeneric collection with mixed items");
        new System.Collections.ArrayList { 1, "a", null }
            .DoSomething()
            .DoSomething();

        Console.WriteLine("nongeneric collection with .. bits");
        new System.Collections.BitArray(0x6D)
            .DoSomething()
            .DoSomething();
    }
}

public static class MyGenericUtils
{
    public static System.Collections.IEnumerable DoSomething(this System.Collections.IEnumerable items)
    {
        // check the REAL type of incoming collection
        // if it implements IEnumerable<T>, we're lucky!
        // we can unwrap it
        // ...usually. How to unwrap it if it implements it multiple times?!
        var ietype = items.GetType().FindInterfaces((t, args) =>
            t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>),
            null).SingleOrDefault();

        if (ietype != null)
        {
            return
                doSomething_X(
                    doSomething_X((dynamic)items)
                );
                // .doSomething_X() - and since the compile-time type is 'dynamic' I cannot chain
                // .doSomething_X() - it in normal way (despite the fact it would actually compile well)
               // `dynamic` doesn't resolve extension methods!
               // it would look for doSomething_X inside the returned object
               // ..but that's just INTERNAL implementation. For the user
               // on the outside it's chainable
        }
        else
            // uh-oh. no what? it can be array, it can be a non-generic collection
            // like System.Collections.Hashtable .. but..
            // from the type-definition point of view it means it holds any
            // OBJECTs inside, even mixed types, and it exposes them as IEnumerable
            // which returns them as OBJECTs, so..
            return items.Cast<object>()
                .doSomething_X()
                .doSomething_X();
    }

    private static IEnumerable<T> doSomething_X<T>(this IEnumerable<T> valitems)
    {
        // do-whatever,let's just see it being called
        Console.WriteLine("I got <{1}>: {0}", valitems.Count(), typeof(T));
        return valitems;
    }
}

Yes, that's silly. I chained them four (2outsidex2inside) times just to show that the type information is not lost in subsequent calls. The point was to show that the 'entry point' takes a nongeneric IEnumerable and that <T> is resolved wherever it can be. You can easily adapt the code to make it a normal LINQ-to-Objects .Count() method. Similarly, one can write all other operations, too.

This example uses dynamic to let the platform resolve the most-narrow T for IEnumerable, if possible (which we need to ensure first). Without dynamic (i.e. .Net2.0) we'd need to invoke the dosomething_X through reflection, or implement it twice as dosomething_refs<T>():where T:class+dosomething_vals<T>():where T:struct and do some magic to call it properly without actually casting (probably reflection, again).

Nevertheless, it seems that you can get something-like-linq working "directly" on things hidden behind nongeneric IEnumerable. All thanks to the fact that the objects hiding behind IEnumerable still have their own full type information (yeah, that assumption may fail with COM or Remoting). However.. I think settling for IEnumerable<T> is a better option. Let's leave plain old IEnumerable to special cases where there is really no other option.

..oh.. and I actually didn't investigate if the code above is correct, fast, safe, resource-conserving, lazy-evaluating, etc.

like image 1
quetzalcoatl Avatar answered Oct 31 '22 14:10

quetzalcoatl