14

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);
    }
}
Community
  • 1
  • 1
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • Do you really mean `IEnumerable`, or do you mean `IEnumerable` ? If the former, why do you use `` and not `` ? – Maarten Jul 19 '16 at 07:39
  • @Maarten Yes, I mean to use `IEnumerable`, because I need to handle the general case, and support different element types. – Lingxi Jul 19 '16 at 07:42
  • 4
    Why would you even work with `IEnumerable` in the first place? Why not `IEnumerable range = new[] { 1, 2, 3 };` instead? -- Btw. `IEnumerable` *does* box, right? If you `foreach` over a (non-generic) `IEnumerable`, you get an `object`. A *generic* enumerable (`IEnumerable`) on the other hand does *not* box. – Corak Jul 19 '16 at 07:50
  • @Corak As the reply to Maarten's comment has said, *I mean to use `IEnumerable`, because I need to handle the general case, and support different element types.* Concerning `IEnumerable`, I think boxing happens every time it is enumerated, but not at the time it is being assigned. See the test code in the updated question. – Lingxi Jul 19 '16 at 08:03
  • It is not clear what do you want to achieve, but in all cases IEnumerable is prefered way to work with enumerated values. It can all that can IEnumerable and has advantages of generics like no boxing for integers etc. – Kirill Bestemyanov Jul 19 '16 at 08:09
  • @quetzalcoatl This is not a duplicate. If you read more carefully, you will find that I'm not asking why or how come, but a solution to a design problem. – Lingxi Jul 19 '16 at 08:09
  • @quetzalcoatl Updated. Thanks. – Lingxi Jul 19 '16 at 08:14
  • @KirillBestemyanov Agreed. I plan to settle down on `IEnumerable` instead of `IEnumerable`. Now, I just wait and see what the team has to say about this proposal :) – Lingxi Jul 19 '16 at 08:17
  • 1
    Still not sure I understand. You say you want to handle "the general case". Well "the general case" would be `IEnumerable`. So if you now have a method like `doStuff(IEnumerable items){/* ... */}` you change it to `doStuff(IEnumerable items){/* ... */}` and you then have *one* implementation where you can handle `int[]`, `List` or `IEnumerable`, but in a type-safe (and non-boxing) way. – Corak Jul 19 '16 at 09:17
  • "But it incurs performance overhead which is unnecessary in my opinion"? Have your measured the performance overhead to see how relevant it is? – Enigmativity Jul 19 '16 at 09:48
  • @Corak Generics is not a panacea. Type unification via inheritance (i.e., via `object`) still has its value. – Lingxi Jul 19 '16 at 15:19
  • @Enigmativity Not really. The original motivation is support for LINQ extension methods. I just want to be as efficient as possible. – Lingxi Jul 19 '16 at 15:20
  • @Lingxi - That might very well be, and I don't know your exact use. But I do know that by using an untyped `IEnumerable` with value types, you will *always* get a penalty because of boxing. And using `.Cast()` won't be worse. On the other hand if you *do* use a generic `IEnumerable` with value types, you *won't* have that penalty. So by just using `T` instead of `object`, you're at least not worse off, but very probably better off. While still being just as "general". So I simply see no reason not to do that. – Corak Jul 19 '16 at 17:39
  • @Lingxi - I think it is more efficient for you to worry about efficiency when you know you have an issue. – Enigmativity Jul 20 '16 at 00:10

4 Answers4

11

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.

Community
  • 1
  • 1
Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
2

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?

lokusking
  • 7,396
  • 13
  • 38
  • 57
  • Note that by the covariance of `IEnumerable`, this means that if you have an `IEnumerable` where `X` is a reference type, then `.Cast()` will let the very same collection instance through un-altered. I.e. it will in reality be a simple reference conversion on the underlying collection object. For a value type `X`, the use of `.Cast()` necessarily runs through all items and boxes them (lazily, just like `.Select(x => (object)x)`). – Jeppe Stig Nielsen Aug 14 '19 at 20:44
1

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.

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
0

IEnumerable<T> is a generic interface. As long as you're only dealing with generics and types known at compile-time, there's no point in using IEnumerable<object> - either use IEnumerable<int> or IEnumerable<T>, depending entirely on whether you're writing a generic method, or one where the correct type is already known. Don't try to find an IEnumerable to fit them all - use the correct one in the first place - it's very rare for that not to be possible, and most of the time, it's simply a result of bad object design.

The reason IEnumerable<int> cannot be cast to IEnumerable<object> may be somewhat surprising, but it's actually very simple - value types aren't polymorphic, so they don't support co-variance. Do not be mistaken - IEnumerable<string> doesn't implement IEnumerable<object> - the only reason you can cast IEnumerable<string> to IEnumerable<object> is that IEnumerable<T> is co-variant.

It's just a funny case of "surprising, yet obvious". It's surprising, since int derives from object, right? And yet, it's obvious, because int doesn't really derive from object, even though it can be cast to an object through a process called boxing, which creates a "real object-derived int".

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • I have to use `IEnumerable`, because I need to support different element types (i.e., runtime polymorphism). – Lingxi Jul 19 '16 at 08:38
  • @Lingxi If that's the case, use `IEnumerable` as a last resort, and get it by doing `.Cast`. For reference types, it will do a simple cast (since they are co-variant), for value types, it will do the same thing `IEnumerable` does. – Luaan Jul 19 '16 at 08:42