简体   繁体   中英

Generic parameter T not implicitly assignable to object — generic method calling nongeneric overloaded method

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM