从IEnumerable<Object>转换为IEnumerable<string>

6

最近我在C#中发现了一个非常令人惊讶的行为。 我有一个方法,它以IEnumerable<Object>作为参数,我传递了IEnumerable<string>,但这是不可能的。 在C#中,所有东西都可以向上转换为Object,那么为什么这不可能呢? 这对我来说完全令人困惑。 请有人帮我解决这个问题。


怎么可能不行呢?是抛出了ArgumentException吗?另外,如果你要将它声明为Object,使用泛型IEnumerable有什么意义呢? - Jon Limjap
为什么不能将List<string>对象存储在List<object>变量中? - Daniel Earwicker
5个回答

11

技术术语是,C# 3.0及更早版本的泛型是不变的。C# 4.0及以后版本,强制转换有效。

"不变"指的是两个泛型类型之间没有关系,即使它们的泛型参数相关 (即彼此为子类或超类)。

在您的示例中,一个 IEnumerable<object> 和一个 IEnumerable<string> 之间没有类型关系,仅因为 string 是 object 的子类型。它们被认为是两个完全无关的类型,就像字符串和整数一样(它们仍然都是 object 的子类型,但这没什么用)。

对于您遇到的问题,有一些解决方法和例外情况。

首先,如果您使用 .NET 3.0,您可以使用 Cast<T>() 扩展方法将每个字符串单独转换为对象。否则,您可以使用 foreach 并将结果放入要使用的静态类型的新变量中。

其次,数组是引用类型的例外情况,即向接受 object[] 类型的方法传递 string[] 类型应该可行。


1
只是更新这个旧答案,C# 现在支持协变性和逆变性,这是一个有效的转换。 - Paul Tyng

8
正如其他人指出的那样,泛型类型是不变的。IEnumerable可以是协变的,但C#目前不支持指定变体。预计C# 4.0将支持变体,因此未来可能会支持。
现在要解决这个问题,您可以使用LINQ扩展方法Cast()。假设您有一个名为Foo的方法,该方法需要一个IEnumerable。您可以像这样调用它,
Foo(stringEnumerable.Cast<object>());

3
将`IEnumerable`传递给需要`IEnumerable`的函数最简单的方法是通过转换函数,如下所示:
public IEnumerable<object> convert<T>(IEnumerable<T> enumerable)
    {
    foreach (T o in enumerable)
        yield return o;
    }

当C# 4发布时,这将不再是必需的,因为它将支持协变和逆变。

2
这正是 Cast<> 的用途 :) - Jon Skeet

0

你应该使用

IEnumerable<T> 

如果你想传入不同的类型,那么你可以查询 T 来找出它是什么类型。

0
一个好的思考方式是问自己“如果你能做到这个,会发生什么?”。以以下示例为例:
IEnumerable<String> strings=...;
IEnumerable<Object> objects = strings; // assume this were legal

objects.Add(new Integer(5)); // what the...

我们刚刚把一个整数添加到了一个字符串列表中。编译器这样做是为了保持类型安全。

IEnumerable<T>并没有Add方法... 而在C# 4.0中,你实际上将能够执行此转换,因为声明将更改为IEnumerable<out T>,从而允许它(安全地)逆变。 - Greg Beech
我们可以替换任何具有Add方法的通用类,例如List。 - 1800 INFORMATION
在.NET 4中,IEnumerable<T> 变成了 IEnumerable<out T>,这是因为你的论点对于容器来说是有道理的,但对于枚举来说则不然。 - Sam Harwell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接