28

Why is the last line not allowed?

IEnumerable<double> doubleenumerable = new List<double> { 1, 2 };
IEnumerable<string> stringenumerable = new List<string> { "a", "b" };
IEnumerable<object> objects1 = stringenumerable; // OK
IEnumerable<object> objects2 = doubleenumerable; // Not allowed

Is this because double is a value type that doesn't derive from object, hence the covariance doesn't work?

Does that mean that there is no way to make this work:

public interface IMyInterface<out T>
{
    string Method(); 
}

public class MyClass<U> : IMyInterface<U>
{
    public string Method()
    {
        return "test";
    }
}

public class Test
{
    public static object test2() 
    {
        IMyInterface<double> a = new MyClass<double>();
        IMyInterface<object> b = a; // Invalid cast!
        return b.Method();
    }
}

And that I need to write my very own IMyInterface<T>.Cast<U>() to do that?

edeboursetty
  • 5,669
  • 2
  • 40
  • 67
  • possible duplicate of [Why covariance and contravariance do not support value type](http://stackoverflow.com/questions/12454794/why-covariance-and-contravariance-do-not-support-value-type) – nawfal Jul 07 '14 at 07:18

3 Answers3

47

Why is the last line not allowed?

Because double is a value type and object is a reference type; covariance only works when both types are reference types.

Is this because double is a value type that doesn't derive from object, hence the covariance doesn't work?

No. Double does derive from object. All value types derive from object.

Now the question you should have asked:

Why does covariance not work to convert IEnumerable<double> to IEnumerable<object>?

Because who does the boxing? A conversion from double to object must box the double. Suppose you have a call to IEnumerator<object>.Current that is "really" a call to an implementation of IEnumerator<double>.Current. The caller expects an object to be returned. The callee returns a double. Where is the code that does the boxing instruction that turns the double returned by IEnumerator<double>.Current into a boxed double?

It is nowhere, that's where, and that's why this conversion is illegal. The call to Current is going to put an eight-byte double on the evaluation stack, and the consumer is going to expect a four-byte reference to a boxed double on the evaluation stack, and so the consumer is going to crash and die horribly with an misaligned stack and a reference to invalid memory.

If you want the code that boxes to execute then it has to be written at some point, and you're the person who gets to write it. The easiest way is to use the Cast<T> extension method:

IEnumerable<object> objects2 = doubleenumerable.Cast<object>();

Now you call a helper method that contains the boxing instruction that converts the double from an eight-byte double to a reference.

UPDATE: A commenter notes that I have begged the question -- that is, I have answered a question by presupposing the existence of a mechanism which solves a problem every bit as hard as a solution to the original question requires. How does the implementation of Cast<T> manage to solve the problem of knowing whether to box or not?

It works like this sketch. Note that the parameter types are not generic:

public static IEnumerable<T> Cast<T>(this IEnumerable sequence) 
{
    if (sequence == null) throw ...
    if (sequence is IEnumerable<T>) 
        return sequence as IEnumerable<T>;
    return ReallyCast<T>(sequence);
}

private static IEnumerable<T> ReallyCast<T>(IEnumerable sequence)
{
    foreach(object item in sequence)
        yield return (T)item;
}

The responsibility for determining whether the cast from object to T is an unboxing conversion or a reference conversion is deferred to the runtime. The jitter knows whether T is a reference type or a value type. 99% of the time it will of course be a reference type.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 6
    Off topic, but I wish you tweeted. – Joe Mar 13 '12 at 16:48
  • 1
    1+ As usual faster with posting. I like the "so the consumer is going to crash and die horribly with an misaligned stack and a reference to invalid memory" part. – Felix K. Mar 13 '12 at 16:50
  • 17
    @JoeTuskan: When I have something to say that fits into 120 characters and is of general interest, I will. Expect a long wait. – Eric Lippert Mar 13 '12 at 17:02
  • 3
    @EricLippert, perhaps a link to a blog about how you use graph theory to improve the efficiency of your laundry. – Joe Mar 13 '12 at 17:09
  • Ok, great explanation, thanks! I was suspecting something about boxing, but that's clear now. I'll do the Cast<> wrapper in my case then. – edeboursetty Mar 13 '12 at 17:11
  • 1
    @EricLippert One Question about that cast operator, at compile time of Cast<> extension method he doesn't know if the source type is a value type, so how does he know if he should use `castclass` or `box`? – Felix K. Mar 13 '12 at 18:26
  • 2
    @FelixK: The Cast extension method first takes an easy out; if the object already is a sequence of the desired type then we use that. Otherwise, it uses the non-generic IEnumerable to treat the sequence as a sequence of objects, and casts each object to T. The jitter is responsible for figuring out whether the cast from object to T is to be implemented as an unboxing conversion or as a reference conversion. – Eric Lippert Mar 13 '12 at 19:22
  • I think your edit explains how something like `Cast()` would know to *unbox*, but not how `Cast()` knows to *box*. The boxing happens in the implementation of the non-generic `IEnumerator.Current` for `List`'s enumerator. – Quartermeister Mar 13 '12 at 22:24
  • @Quartermeister: Your analysis is correct. But this does not present any problem; suppose there was no non-generic IEnumerable. Then we'd say `IE ReallyCast(IE s) { foreach(object i in s) yield return (T)i; }` -- same thing. We can always cast an S to object; the jitter takes care of turning that into either a boxing or a no-op depending on what type is supplied for S at runtime. – Eric Lippert Mar 13 '12 at 22:48
  • @EricLippert Converting a value type into a object would cause a performance loss for the `Cast()`-method, or i'm wrong? I tested it with 100.000 values and about 1000 times, and it seems that a custom cast method ( which takes a delegate for conversion ) is faster when converting from Double to Object. Of course it can handle other conversions like double to single ( Which cannot be done by the .NET method ). I think it's the same when using select instead of cast. – Felix K. Mar 14 '12 at 09:10
5

To understand what is allowed and not allowed, and why things behave as they do, it is helpful to understand what's going on under the hood. For every value type, there exists a corresponding type of class object, which--like all objects--will inherit from System.Object. Each class object includes with its data a 32-bit word (x86) or 64-bit longword (x64) which identifies its type. Value-type storage locations, however, do not hold such class objects or references to them, nor do they have a word of type data stored with them. Instead, each primitive-value-type location simply holds the bits necessary to represent a value, and each struct-value-type storage location simply holds the contents of all the public and private fields of that type.

When one copies a variable of type Double to one of type Object, one creates a new instance of the class-object type associated with Double and copies all the bytes from the original to that new class object. Although the boxed-Double class type has the same name as the Double value type, this does not lead to ambiguity because they can generally not be used in the same contexts. Storage locations of value types hold raw bits or combinations of fields, without stored type information; copying one such storage location to another copies all bytes, and consequently copies all public and private fields. By contrast, heap objects of types derived from value types are heap objects, and behave like heap objects. Although C# regards the contents of value-type storage locations as though they are derivatives of Object, under the hood the contents of such storage locations are simply collections of bytes, effectively outside the type system. Since they can only be accessed by code which knows what the bytes represent, there is no need to store such information with the storage location itself. Although the necessity for boxing when calling GetType on a struct is often described in terms of GetType being a non-shadowed, non-virtual function, the real necessity stems from the fact that the contents of a value-type storage location (as distinct from the location itself) don't have type information.

supercat
  • 77,689
  • 9
  • 166
  • 211
0

Variance of this type is only supported for reference types. See http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122