7

What is common rule to check if IEnumerable<T1> covariant to IEnumerable<T2> ?

I've made some experiments:


1.

Object Obj = "Test string";
IEnumerable<Object> Objs = new String[100];

Works because IEnumerable<out T> is covariant and String inherits Object.

2.

interface MyInterface{}
struct MyStruct:MyInterface{}
.....
Object V = new MyStruct();
Console.WriteLine(new MyStruct() is Object); // Output: True. 
IEnumerable<Object> Vs = new MyStruct[100]; // Compilation error here

MyStruct is actually an Object, but it does not work because Object is reference type and MyStruct is value type. OK, I see some logic here.

3.

Console.WriteLine(new MyStruct() is ValueType); // Output: "True"
ValueType V2 = new MyStruct();
IEnumerable<ValueType> Vs2 = new MyStruct[100]; // Compilation error here

Should work because IEnumerable<out T> is covariant and MyStruct IS ValueType, but does not work... OK, maybe MyStruct does not actually inheritst ValueType....

4.

MyInterface V3 = new MyStruct(); 
Console.WriteLine(V3 is MyInterface); // Output: "True" 
IEnumerable<MyInterface> Vs3 = new MyStruct[100]; // Compilation error here

Even this way: "Cannot convert MyStruct to MyInterface". Oh, really?? You just did it one line before...


I've tried to formulate common rule:

public static bool IsCovariantIEnumerable(Type T1, Type T2  ){          
    return (T2.IsAssignableFrom(T1)) && !T2.IsValueType; // Is this correct??
}

So, questions is how to actually determine if IEnumerable<T1> covariant to IEnumerable<T2>? Is my IsCovariantIEnumerable(...) function correct? If yes, is there any simplier way to check it? If not, how to fix it?

See also these articles: 1, 2.

Community
  • 1
  • 1
Astronavigator
  • 2,021
  • 2
  • 24
  • 45
  • Value types simply do not support co-variance; it shouldn't be suprising given that value types do not support *inheritance* at all. If you need to use a value type as an interface and/or `object`, you need to box it - e.g. `array.Cast`. It's not clear what part of this you don't understand, given that the questions you linked answer this very question as well :) – Luaan Aug 30 '16 at 11:02
  • @Luaan, I just want to have complete rule to check if `IEnumerable` covariant to `IEnumerable`. I am still not sure if my `IsCovariantIEnumerable()` function is correct. – Astronavigator Aug 30 '16 at 11:06
  • 1
    In terms of supporting interfaces, [`IsAssignableFrom`](https://msdn.microsoft.com/en-us/library/system.type.isassignablefrom(v=vs.110).aspx) might do better than `IsSubclassOf` – grek40 Aug 30 '16 at 11:07
  • @grek40, yes, I missed interfaces. – Astronavigator Aug 30 '16 at 11:09
  • Please note that your example 4 is not working because `MyStruct` is a `struct`. If it is a `class` it works fine, so it is consistent with the first example. – fknx Aug 30 '16 at 11:16
  • @fknx, yes, but `MyStruct` implements `MyInterface`. That is why I've tried it. – Astronavigator Aug 30 '16 at 11:20

2 Answers2

5

In your specific case it does not work because value types do not support co-variance.

But for the question how to determine if an IEnumerable<T2> is co-variant to IEnumerable<T1>:

The method Type.IsAssignableFrom() tells you if an instance of a certain type is assignable to a variable of this type. So you can implement your method like that:

public static bool IsCovariantIEnumerable(Type T1, Type T2)
{
    Type enumerable1 = typeof(IEnumerable<>).MakeGenericType(T1);
    Type enumerable2 = typeof(IEnumerable<>).MakeGenericType(T2);
    return enumerable1.IsAssignableFrom(enumerable2);
}

Usage:

if (IsCovariantIEnumerable(typeof(object), typeof(string))
    Console.WriteLine("IEnumerable<string> can be assigned to IEnumerable<object>");

But IsCovariantIEnumerable(typeof(object), typeof(MyStruct)) will return false for the reason stated above.


For completeness: Of course you don't need an extra method as you can easily do typeof(IEnumerable<object>).IsAssignableFrom(typeof(IEnumerable<string>).

onicofago
  • 302
  • 5
  • 12
René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • Actually checking the `IEnumerable<...>` type for assignability is definitely more straight forward than second guessing the item type behavior. Clean solution, I didn't think of. – grek40 Aug 30 '16 at 11:20
  • @grek40 You're right, I was thinking about OP's method implementation and thereby forgot the straight call to `IsAssignableFrom`. Added it for completeness. – René Vogt Aug 30 '16 at 11:22
  • I see that your `IsCovariantIEnumerable` works fine. But still question is: "Is my `IsCovariantIEnumerable` also correct or not?" Are there any examples where your function works, but mine does not? – Astronavigator Aug 30 '16 at 11:31
  • @Astronavigator as it stands now I think you're one is correct, too. (I only read your first version using `IsSubClass` before answering). – René Vogt Aug 30 '16 at 11:37
  • @RenéVogt, yes, I've corrected it after grek40's comment. – Astronavigator Aug 30 '16 at 11:46
2

Value types do not support covariance as it would change their internal representation [1].

If you want to avoid weird cases i would recommend using IsAssignableFrom instead:

public static bool IsCovariantIEnumerable(Type T1, Type T2) => T1.IsAssignableFrom(T2);

Community
  • 1
  • 1
krontogiannis
  • 1,819
  • 1
  • 12
  • 17