如何有效地转换 List<T>?

26
我有一个

List<InputField> 
但我需要一个。
List<IDataField>  

有没有办法在C#中进行转换?或者使用Linq获得相同的结果?

我有两个实现相同接口的类:

interface IDataField { }
class InputField : IDataField { }
class PurchaseField : IDataField { }

这个列表来自于一个 Linq-to-Sql 查询:

List<InputField> list = (from i .... select i).ToList();

嗯,也许更明智的做法是先弄清楚为什么需要一个InputField列表而不是IDataField列表?或者真的有必要吗? - Erik Lieben
我认为这是必要的。InputField和PurchaseField是两个不同的东西,但有两个共同点。IDataField表达了这一点。我有一些复杂的逻辑,只对IDataField进行操作。 - bentford
大家注意:这些都是非常好的答案!感谢你们的快速回复!!! - bentford
8个回答

47
.OfType<T> 和 .Cast<T> 都会返回一个 T 类型的列表,但这两种方法的意义是不同的。 list.OfType() 过滤原始列表,并返回所有类型为 T 的项目,并跳过那些不属于该类型的项目。 list.Cast() 原始列表中的所有项目转换为类型 T,并对无法转换为该类型的项目抛出异常。
在您的情况下,两者都会给出相同的结果,但使用 .Cast() 会更清晰地传达您的意图,因此我建议您使用它。
List<InputField> list = (from i .... select i).Cast<IDataField>().ToList();

1
该回答比我快了35秒,真是太棒了。已点赞 - 这是正确的答案,因为OP正在寻找一个转换。 - Erik Forbes
我接受了这个答案,因为我确实在寻找一个转换。感谢大家。 - bentford

13
List<InputField> raw = (from i .... select i).ToList();
List<IDataField> result = raw.OfType<IDataField>().ToList();

9

2
正如我即将所说的那样。请注意,在C# 2.0中,您需要显式指定泛型,但在C# 3.0中(如上所述),它是隐含的。 - Marc Gravell

5

由于该列表来自

List<InputField> list = (from i .... select i).ToList();

你能否将“select i”部分更改为返回IDataField呢? 类似这样:
List<InputField> list = (from i .... select (IDataField)i).ToList();

如果那不起作用,也许IEnumerable的“Cast”扩展可以解决问题:

List<DataField> list2 = list.Cast<IDataField>();

实际上,修复查询中的“select”部分是最好的选择。否则,请查看Helen Toomik的答案,了解OfType和Cast之间的区别... - Arjan Einbu

3
以防万一:我对C#的经验很少,但如果这个泛型构造与Java中的意思相同,那么您必须创建一个由超类型参数化的全新列表。换句话说,如果每个Bangle实例也是Akthud的实例,则并不意味着每个List<Bangle>也是List<Akthud>
原因是您可以有两个对该List<Bangle>的引用。如果第二个引用将其转换并将其视为List<Akthud>,则允许向其中添加Akthud - 但现在第一个引用具有List<Bangle>,其成员并非全部为Bangle。违反了规定!
话虽如此,根据我所知,David B的答案确实可以正确地完成您想要的操作。(它看起来像是一次复制操作。)
[如果我误解了C#泛型的语义,希望有人在评论中纠正我!]

你说得对 - C# 中的泛型既不是协变的也不是逆变的;这是他们在 C# 4 中要解决的问题。这里有一篇关于此的 MSDN 文章:http://msdn.microsoft.com/zh-cn/library/ms228359(VS.80).aspx - Erik Forbes
不错的链接。我认为你是想用这个作为泛型协变和逆变问题的例子,而不是关于C# 4变化的(我在那里没有看到任何相关内容)。只是为了澄清一下(?)。不过仍然非常有启发性。 - Paul Brinkley

1

我不知道直接转换是否会产生预期的效果。在我做过这种事情的极少数情况下,通常是这样的:

List<InputField> list = .....
List<IDataField> list2 = new (List<IDataField>((IDataField[])list.ToArray()));

1

ConvertAll 似乎是更好的解决方案,因为它不依赖于 Linq 库,而且更短更直观。

但两者都是一种变通方法,而不是真正的解决方案。它们都会创建一个包含相同数据的新集合。.net 应该支持通用协变性,即通用集合中的向上转型,这将使您可以自然地完成此操作。例如:

List<IDataField> ls = (List<IDataField>)List<InputField>

从我的搜索结果来看,我的结论是,截至3.5版,.Net不支持此功能。因此我们必须采用解决方法。
以下是有关该主题的讨论:

0

我有以下字符串

 var str = "2 4 7 8 10";

当我进行如下转换时

var numbersInt2 = numbers.Split(' ')
                .ToList().Cast<int>().ToList();

它会返回一个异常,但是

System.InvalidCastException:“指定的转换无效。”

如果使用以下内容:

var numbersInt = numbers.Split(' ')
                .ToList().ConvertAll(int.Parse);

它可行。


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