在C#中,为什么无法将List<string>对象存储在List<object>变量中?

86

在C#中,似乎无法将List对象存储在List变量中,甚至无法明确地进行强制类型转换。

List<string> sl = new List<string>();
List<object> ol;
ol = sl;

结果导致无法隐式转换类型 System.Collections.Generic.List<string>System.Collections.Generic.List<object>

然后是...

List<string> sl = new List<string>();
List<object> ol;
ol = (List<object>)sl;

导致错误:无法将类型 System.Collections.Generic.List<string> 转换为 System.Collections.Generic.List<object>

当然,你可以逐个取出字符串列表中的每个元素并一个一个地放回去,但这是一个相当复杂的解决方案。


5
这将会在C# 4.0中发生变化,因此您可能需要查阅协变性和逆变性。这将以一种类型安全的方式允许这些操作。 - John Leidegren
更或少重复:https://dev59.com/5UXRa4cB1Zd3GeqPvNt9 - Joel in Gö
7
John说:C#4不允许这样做。想想ol.Add(new object()); - Guillaume
这个问题在这里被Jon Skeet回答了:https://dev59.com/xHI-5IYBdhLWcg3wED5V#2033921 - JCH2k
14个回答

39

这么想,如果您执行此类转换并向列表添加类型为 Foo 的对象,则字符串列表不再一致。如果您迭代第一个引用,当遇到 Foo 实例时,将会抛出类转换异常,因为无法将 Foo 转换为字符串!

顺便说一下,我认为更重要的是能否完成反向转换:

List<object> ol = new List<object>();
List<string> sl;
sl = (List<string>)ol;

我有一段时间没有使用C#了,所以不确定这是否合法,但这种类型转换实际上(潜在地)是有用的。在这种情况下,您正在从一个更通用的类(object)转换为一个更具体的类(string),该类从通用类扩展而来。通过这种方式,如果您将字符串列表添加到对象列表中,您就不会违反对象列表。

有人知道或者可以测试这种类型转换在C#中是否合法吗?


4
我认为你的例子存在同样的问题。如果 ol 中有非字符串内容会怎么样?问题在于 List 的某些方法,如添加/插入,可以正常工作,但迭代可能是一个真正的问题。 - Chris Ammerman
3
Eric Lippert在他的博客中有一系列关于这个话题的精彩文章:为什么在泛型方法中添加协变性和逆变性约束可能是可行的,但在类级别上可能永远无法按照我们希望的方式工作。http://is.gd/3kQc - Chris Ammerman
@ChrisAmmerman:如果ol中有非字符串的内容,我想我会预期在运行时转换失败。但是,如果转换成功,然后向ol添加了非字符串的内容,那么你真正会遇到麻烦。因为sl引用相同的对象,现在你的List<string>将包含一个非字符串。问题在于Add,我想这解释了为什么这段代码不会编译,但如果你将List<object> ol更改为IEnumerable<object> ol,它将编译通过,因为它没有Add。(我在C# 4中检查过这一点。) - Tim Goodman
有了这个更改,它可以编译,但是会抛出“InvalidCastException”异常,因为“ol”的运行时类型仍然是“List<object>”。 - Tim Goodman

36

如果您正在使用.NET 3.5,请查看Enumerable.Cast方法。它是一种扩展方法,因此您可以直接在List上调用它。

List<string> sl = new List<string>();
IEnumerable<object> ol;
ol = sl.Cast<object>();

这不完全是您要求的,但应该能解决问题。

编辑:如 Zooba 所指出的,您可以调用 ol.ToList() 来获取一个 List。


14

您无法在具有不同类型参数的泛型类型之间进行强制转换。专门化的泛型类型不属于同一继承树,因此是不相关的类型。

在 .NET 3.5 之前实现此操作:

List<string> sl = new List<string>();
// Add strings to sl

List<object> ol = new List<object>();

foreach(string s in sl)
{
    ol.Add((object)s);  // The cast is performed implicitly even if omitted
}

使用Linq:

var sl = new List<string>();
// Add strings to sl

var ol = new List<object>(sl.Cast<object>());

// OR
var ol = sl.Cast<object>().ToList();

// OR (note that the cast to object here is required)
var ol = sl.Select(s => (object)s).ToList();

11

原因是像 List<> 这样的泛型类在大多数情况下被外部视为普通类。例如,当您使用 List<string>() 时,编译器会使用 ListString()(其中包含字符串)。[技术人员:这是正在发生的事情的极简化版本]

因此,很明显编译器无法聪明到通过转换其内部集合的项来将 ListString 转换为 ListObject。

这就是为什么有针对 IEnumerable 的扩展方法,例如 Convert(),允许您轻松地为存储在集合中的项目提供转换,这可能只需要从一个类型强制转换为另一个类型。


6
这与协变有很大关系,例如,泛型类型被视为参数,如果参数不能正确解析为更具体的类型,则操作将失败。这意味着您实际上无法转换为更一般的类型,如对象。正如Rex所述,List对象不会为您转换每个对象。您可能想尝试以下代码:
List<string> sl = new List<string>();
//populate sl
List<object> ol = new List<object>(sl);

或者:

List<object> ol = new List<object>();
ol.AddRange(sl);

理论上,ol标签将会无误地复制sl标签中的所有内容。


5

是的,你可以在.NET 3.5中实现:

List<string> sl = new List<string>();
List<object> ol = sl.Cast<object>().ToList();

3

2

1

泛型上的协变不受支持,但您实际上可以使用数组来实现:

object[] a = new string[] {"spam", "eggs"};

C# 在运行时执行检查,以防止您将一个 int 放入 a 中。


1

这样做实际上是为了防止您尝试将任何奇怪的“对象”放入您的“ol”列表变量中(因为List<object>似乎允许),因为那样会导致代码崩溃(因为列表确实是List<string>,只接受String类型的对象)。这就是为什么您不能将变量转换为更一般的规范。

在Java中情况恰恰相反,您没有泛型,而是运行时的对象列表,您真的可以将任何奇怪的对象塞入您本应严格类型化的列表中。搜索“具体化的泛型”以查看有关Java问题的更广泛讨论...


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