将List <DerivedClass>转换为List <BaseClass>

虽然我们可以从基类/接口inheritance,但为什么我们不能使用相同的类/接口声明一个List<>

 interface A { } class B : A { } class C : B { } class Test { static void Main(string[] args) { A a = new C(); // OK List<A> listOfA = new List<C>(); // compiler Error } } 

有没有办法?

做这个工作的方法是迭代列表并投射元素。 这可以使用ConvertAll完成:

 List<A> listOfA = new List<C>().ConvertAll(x => (A)x); 

你也可以使用Linq:

 List<A> listOfA = new List<C>().Cast<A>().ToList(); 

首先,停止使用不可能理解的类名,如A,B,C。使用动物,哺乳动物,长颈鹿或食物,水果,橙色或其他关系清晰的地方。

那么你的问题是“为什么我不能把一个长颈鹿分配给一个动物types列表的variables,因为我可以给一个动物types的variables分配一个长颈鹿?

答案是:假设你可以。 那么会出现什么问题呢?

那么,你可以添加一个老虎动物列表。 假设我们允许你把一个长颈鹿列表放在一个拥有动物列表的variables中。 然后,你尝试添加一个老虎到列表中。 怎么了? 你想要长颈鹿的名单包含一只老虎吗? 你想要崩溃吗? 或者你想让编译器保护你免于崩溃,首先使这项任务非法?

我们select后者。

这种转换被称为“协变”转换。 在C#4中,我们将允许您在已知转换始终安全的情况下在接口和委托上进行协变转换。 有关详细信息,请参阅我的关于协方差和逆变的博客文章。 (这个星期一和星期四,这个话题会有新鲜的。)

引用Eric的伟大解释

怎么了? 你想要长颈鹿的名单包含一只老虎吗? 你想要崩溃吗? 或者你想让编译器保护你免于崩溃,首先使这项任务非法? 我们select后者。

但是如果你想select运行时崩溃而不是编译错误呢? 您通常会使用Cast <>或ConvertAll <>,但是您将遇到两个问题:它将创build列表的副本。 如果您在新列表中添加或删除某些内容,这将不会反映在原始列表中。 其次,由于它与现有对象一起创build了一个新的列表,所以存在很大的性能和内存损失。

我有同样的问题,因此我创build了一个包装类,可以投射一个通用的列表,而不创build一个全新的列表。

在原来的问题中,你可以使用:

 class Test { static void Main(string[] args) { A a = new C(); // OK IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok! } } 

在这里包装类(+扩展方法CastList为便于使用)

 public class CastedList<TTo, TFrom> : IList<TTo> { public IList<TFrom> BaseList; public CastedList(IList<TFrom> baseList) { BaseList = baseList; } // IEnumerable IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); } // IEnumerable<> public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); } // ICollection public int Count { get { return BaseList.Count; } } public bool IsReadOnly { get { return BaseList.IsReadOnly; } } public void Add(TTo item) { BaseList.Add((TFrom)(object)item); } public void Clear() { BaseList.Clear(); } public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); } public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); } public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); } // IList public TTo this[int index] { get { return (TTo)(object)BaseList[index]; } set { BaseList[index] = (TFrom)(object)value; } } public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); } public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); } public void RemoveAt(int index) { BaseList.RemoveAt(index); } } public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo> { public IEnumerator<TFrom> BaseEnumerator; public CastedEnumerator(IEnumerator<TFrom> baseEnumerator) { BaseEnumerator = baseEnumerator; } // IDisposable public void Dispose() { BaseEnumerator.Dispose(); } // IEnumerator object IEnumerator.Current { get { return BaseEnumerator.Current; } } public bool MoveNext() { return BaseEnumerator.MoveNext(); } public void Reset() { BaseEnumerator.Reset(); } // IEnumerator<> public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } } } public static class ListExtensions { public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list) { return new CastedList<TTo, TFrom>(list); } } 

至于为什么它不起作用,可能有助于理解协方差和逆变 。

只是为了说明为什么这不起作用,这里是对你提供的代码的改变:

 void DoesThisWork() { List<C> DerivedList = new List<C>(); List<A> BaseList = DerivedList; BaseList.Add(new B()); C FirstItem = DerivedList.First(); } 

应该这样工作吗? 列表中的第一项是types“B”,但DerivedList项目的types是C.

现在,假设我们真的只想创build一个generics函数,它在一个实现了A的types的列表上运行,但是我们不关心它是什么types:

 void ThisWorks<T>(List<T> GenericList) where T:A { } void Test() { ThisWorks(new List<B>()); ThisWorks(new List<C>()); } 

如果你使用IEnumerable来代替,它将起作用(至less在C#4.0中,我还没有尝试过以前的版本)。 这只是一个演员,当然,这仍然是一个列表。

代替 –

List<A> listOfA = new List<C>(); // compiler Error

在问题的原始代码中,使用 –

IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)

您只能转换为只读列表。 例如:

 IEnumerable<A> enumOfA = new List<C>();//This works IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works IReadOnlyList<A> ro_listOfA = new List<C>();//This works 

而你不能用它来支持保存元素的列表。 原因是:

 List<string> listString=new List<string>(); List<object> listObject=(List<object>)listString;//Assume that this is possible listObject.Add(new object()); 

现在怎么办? 请记住,listObject和listString实际上是相同的列表,所以listString现在有对象元素 – 它不应该是可能的,它不是。

因为C#不允许这种types的 遗产 转换。

这是BigJim辉煌答案的延伸。

在我的情况下,我有一个与Children字典的NodeBase类,我需要一种方法来从孩子一般O(1)查找。 我试图返回一个私人字典字段在Children的getter,所以显然我想避免昂贵的复制/迭代。 因此,我使用Bigjim的代码将Dictionary<whatever specific type> Dictionary<NodeBase>转换为genericsDictionary<NodeBase>

 // Abstract parent class public abstract class NodeBase { public abstract IDictionary<string, NodeBase> Children { get; } ... } // Implementing child class public class RealNode : NodeBase { private Dictionary<string, RealNode> containedNodes; public override IDictionary<string, NodeBase> Children { // Using a modification of Bigjim's code to cast the Dictionary: return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>(); } ... } 

这工作得很好。 然而,我最终遇到了无关的限制,最终在基类中创build了一个抽象的FindChild()方法来代替查找。 事实certificate,这首先消除了铸造字典的需要。 (为了我的目的,我可以用一个简单的IEnumerablereplace它。)

所以你可能会问的问题(特别是如果性能是禁止使用.Cast<>.ConvertAll<> )的问题是:

“我是否真的需要将整个集合进行投射,还是可以使用抽象方法来保存执行任务所需的特殊知识,从而避免直接访问集合?

有时最简单的解决scheme是最好的。