泛型参数 T 不能隐式分配给对象——泛型方法调用非泛型重载方法

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

既然 object 是 C# 最终基础 class,为什么 System.Collections.Generic.IEnumerable<T> 不能分配给参数类型 System.Collections.Generic.IEnumerable<object>


我在做类似于以下代码的事情时偶然发现了这种好奇心。这是调用重载非泛型方法的泛型方法。

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");
}

我不明白为什么泛型方法不应该调用第一个重载而不是调用第二个重载。我认为编译器应该能够说任何 <T> 都可以隐式转换为 object,因此 IEnumerable<T> 应该可以隐式转换为 IEnumerable<object>.

换句话说:

IEnumerable<object> casted = (IEnumerable<object>) objects; 

为什么需要这一行来调用方法重载 1?鉴于该对象是最终基础,C# 不能自动执行此操作 class?

是不是因为 C# 假设​​我可能传递了一个与类型 object 不兼容的 <T> —— 尽管实际上一切都是 object

IEnumerable 不是 IEnumerable 的超 class(无论 T 是什么)。您不能将第二个分配给第一种类型的变量,因为它们被认为是完全不同的。在获得匹配的签名之前,您需要将 IEnumerable 的类型转换为对象:

void MethodCaller<T> (IEnumerable<T> objects) 
{
    Method(objects.Cast<object>()); 
}

让我们在这里将重载解决方案排除在等式之外。这大约是 generic variance。特别是,您期望从 IEnumerable<T>IEnumerable<object>.

进行隐式转换

这不起作用,因为泛型变体仅在类型参数已知为引用类型时才起作用。来自链接文档:

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.

例如,这很好:

IEnumerable<string> strings = ...;
IEnumerable<object> objects = strings;

但这失败了:

IEnumerable<int> ints = ...;
IEnumerable<object> objects = ints;

在您的一般情况下,T 可以是任何类型 ,包括值类型 。这就是它失败的原因。如果使用 where T : class 约束将 T 约束为引用类型,则可以。

具体来说,这是无效的:

static void Foo<T>(IEnumerable<T> ts)
{
    IEnumerable<object> objects = ts;
}

但这是有效的:

static void Foo<T>(IEnumerable<T> ts) where T : class
{
    IEnumerable<object> objects = ts;
}