Why is System.Collections.Generic.IEnumerable<T>
is not assignable to parameter type System.Collections.Generic.IEnumerable<object>
, given that object
is C# ultimate base class?
I stumbled upon this curiosity when doing something similar to the following code. It's a generic method calling an overloaded non-generic method.
void Main()
{
List<object> objects = new List<object>();
Method(objects); // This calls the method with IEnumerable, as expected
MethodCaller(objects);
}
void MethodCaller<T> (IEnumerable<T> objects)
{
Method(objects); // Calls method overload 2, with object as parameter - why?
// Why is the following line required to call the method overload 1?
// Can't C# do this automatically, given that object is the ultimate base class for everything?
IEnumerable<object> casted = (IEnumerable<object>) objects;
Method(casted); // Calls method overload 1
}
void Method (IEnumerable<object> param)
{
// Method overload 1
Console.WriteLine("Method overload 1 - with IEnumerable<object> as parameter");
}
void Method (object param)
{
// Method overload 2
Console.WriteLine("Method overload 2 - with object as parameter");
}
I don't understand why the generic method should not be calling the first overload instead of the second. I thought that the compiler should be able to say that any <T>
can be implicitly cast to object
, therefore IEnumerable<T>
should be implicitly castable to IEnumerable<object>
.
In other words:
IEnumerable<object> casted = (IEnumerable<object>) objects;
Why is this line required to call the method overload 1? Can't C# do that automatically, given that object is the ultimate base class?
Is it because C# assumes that I might be passing a <T>
which is not compatible with the type object
-- even though everything is actually object
?
Let's take overload resolution out of the equation here. This is about generic variance . In particular, you're expecting there to be an implicit conversion from IEnumerable<T>
to IEnumerable<object>
.
That doesn't work, because generic variance only works when the type arguments are known to be reference types. From the linked documentation:
Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.
So for example, this is fine:
IEnumerable<string> strings = ...;
IEnumerable<object> objects = strings;
But this fails:
IEnumerable<int> ints = ...;
IEnumerable<object> objects = ints;
In your generic case, T
could be any type including a value type . That's why it fails. If you constrain T
to be a reference type using the where T : class
constraint, it's fine.
So to be concrete, this is invalid:
static void Foo<T>(IEnumerable<T> ts)
{
IEnumerable<object> objects = ts;
}
But this is valid:
static void Foo<T>(IEnumerable<T> ts) where T : class
{
IEnumerable<object> objects = ts;
}
IEnumerable< object> is not a super class of IEnumerable< T> (whatever T is). You can't assign the second to a variable of the first type as they are considered to be completely different. You need to cast the type of your IEnumerable to object before you will get a matching signature like:
void MethodCaller<T> (IEnumerable<T> objects)
{
Method(objects.Cast<object>());
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.