将 List<DerivedClass> 转换为 List<BaseClass>
Posted
技术标签:
【中文标题】将 List<DerivedClass> 转换为 List<BaseClass>【英文标题】:Convert List<DerivedClass> to List<BaseClass> 【发布时间】:2010-12-21 11:57:18 【问题描述】:虽然我们可以从基类/接口继承,但为什么我们不能声明 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
有办法吗?
【问题讨论】:
***.com/questions/16966961/… 【参考方案1】:对于您的问题,有几种 native C# 可能性:
dynamic
Array
IReadOnlyList
, IEnumerable
正确使用List<>
。
它们都运行良好!不需要任何棘手的编程!
以下是每个示例:
1. dynamic
:最通用的解决方案
运行时类型检查
您放弃了编译器错误检查支持,因此请小心处理!如果您尝试添加错误类型的元素,您只会收到运行时错误!
您甚至可以分配不相关类的集合。
只需写dynamic listOfA = new List<C>();
而不是List<A> listOfA = new List<C>();
首先是所有示例的接口和类定义:
using System;
using System.Collections.Generic;
using System.Linq;
interface IAnimal
public string Name get;
class Bear : IAnimal
public string BearName = "aBear";
public string Name => BearName;
class Cat : IAnimal
public string CatName = "aCat";
public string Name => CatName;
// Dog has no base class/interface; it isn't related to the other classes
class Dog
public string DogName = "aDog";
public string Name => DogName;
这是使用dynamic
的示例
public class AssignDerivedClass
public static void TestDynamicListAndArray()
dynamic any = new List<Bear>() // List of derived
new Bear() BearName = "Bear-1" ,
new Bear() BearName = "Bear-2"
;
//any[0].CatName = "NewCat"; // => Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
Console.WriteLine($"Bear names: any[0].BearName, Name(any[1])");
any = new Cat[] // Array of derived
new Cat() CatName = "Cat-3" ,
new Cat() CatName = "Cat-4"
;
Console.WriteLine($"Cat names: any[0].CatName, any[1].Name");
any = new List<Dog>() // List of non-related class
new Dog() DogName = "Dog-5" ,
new Dog() DogName = "Dog-6"
;
Console.WriteLine($"Dog names: any[0].DogName, Name(any[1])");
any = new List<IAnimal>() // List of interface
// any = new IAnimal[] // Array of interface works the same
new Bear() BearName = "Bear-7" ,
new Cat() CatName = "Cat-8"
;
Console.WriteLine($"Animal names: any[0].BearName, any[1].CatName");
any[0].BearName = "NewBear";
Console.WriteLine($"Animal names: Name(any[0]), any[1].Name");
private static string Name(dynamic anymal)
return anymal switch
Bear bear => bear.BearName,
Cat cat => cat.CatName,
Dog dog => dog.DogName,
_ => "No known Animal"
;
// Bear names: Bear-1, Bear-2
// Cat names: Cat-3, Cat-4
// Dog names: Dog-5, Dog-6
// Animal names: Bear-7, Cat-8
// Animal names: NewBear, Cat-8
2. Array
:创建Bear[]
数组,保证所有数组元素引用Bear
的实例。
您可以交换元素,但不能删除或添加新元素。
尝试设置错误的类型会产生运行时错误。
public static void TestArray()
Bear[] bears = new Bear(), null ;
IAnimal[] bearAnimals = bears;
//bearAnimals[1] = new Cat(); // System.ArrayTypeMismatchException
bearAnimals[1] = new Bear() BearName = "Bear-1" ;
Console.WriteLine($"Bear names: bearAnimals[0].Name, bears[1].BearName");
// Result => Bear names: aBear, Bear-1
3。 IReadOnlyList
,IEnumerable
:
将您的List<C>
分配给IEnumerable<A>
或IReadOnlyList<A>
它们都不能在运行时更改,即您不能添加或删除元素。
当添加元素会导致错误时,为什么编译器应该允许将您的List<C>
分配给List<A>
而不是IReadOnlyList<A>
?
public static void TestIEnumerableAndIReadonlyList()
var cats = new List<Cat>()
new Cat() CatName = "Cat-3" ,
new Cat() CatName = "Cat-4"
;
IEnumerable<IAnimal> iEnumerable = cats;
Console.WriteLine($"Cat names: (iEnumerable.ElementAt(0) as Cat).CatName, "
+ Name(iEnumerable.Last()));
IReadOnlyList<IAnimal> iROList = cats;
Console.WriteLine($"Cat names: iROList[0].Name, Name(iROList[1])");
//iROList.Add(new Cat()); // compiler error CS61: no definition for 'Add'
// Result:
// Cat names: Cat-3, Cat-4
// Cat names: Cat-3, Cat-4
4。正确使用List<>
: List<A> listOfA = new List<A>()
定义你的界面列表
只分配一个派生类的实例 - 你不想存储其他类,是吗?
public static void TestListOfInterface()
var bears = new List<IAnimal>()
new Bear() BearName = "Bear-1" ,
new Cat() CatName = "Cat-3" ,
;
bears.Add(new Bear() BearName = "Bear-2" );
string bearNames = string.Join(", ", bears.Select(animal => animal.Name));
Console.WriteLine($"Bear names: bearNames");
string bearInfo0 = VerifyBear(bears[0]);
string bearInfo1 = VerifyBear(bears[1]);
Console.WriteLine($"One animal is bearInfo0, the other one is bearInfo1");
string VerifyBear(IAnimal bear)
=> (bear as Bear)?.BearName ?? "disguised as a bear!!!";
// Bear names: Bear-1, Cat-3, Bear-2
// One animal is Bear-1, the other one is disguised as a bear!!!
【讨论】:
【参考方案2】:我已经阅读了整篇文章,我只想指出对我来说似乎不一致的地方。
编译器会阻止您使用列表进行赋值:
List<Tiger> myTigersList = new List<Tiger>() new Tiger(), new Tiger(), new Tiger() ;
List<Animal> myAnimalsList = myTigersList; // Compiler error
但是编译器对数组完全没问题:
Tiger[] myTigersArray = new Tiger[3] new Tiger(), new Tiger(), new Tiger() ;
Animal[] myAnimalsArray = myTigersArray; // No problem
关于赋值是否已知是安全的的争论在这里分崩离析。我对数组所做的分配不安全。为了证明这一点,如果我继续这样做:
myAnimalsArray[1] = new Giraffe();
我得到一个运行时异常“ArrayTypeMismatchException”。如何解释这一点?如果编译器真的想阻止我做一些愚蠢的事情,它应该阻止我做数组赋值。
【讨论】:
【参考方案3】:您还可以使用System.Runtime.CompilerServices.Unsafe
NuGet 包创建对相同List
的引用:
using System.Runtime.CompilerServices;
...
class Tool
class Hammer : Tool
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);
鉴于上述示例,您可以使用tools
变量访问列表中现有的Hammer
实例。将Tool
实例添加到列表中会引发ArrayTypeMismatchException
异常,因为tools
引用了与hammers
相同的变量。
【讨论】:
【参考方案4】:我个人喜欢创建带有类扩展的库
public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
where TFrom : class
where TTo : class
return fromlist.ConvertAll(x => x as TTo);
【讨论】:
【参考方案5】:如果您改用IEnumerable
,它会起作用(至少在C# 4.0 中,我没有尝试过以前的版本)。这只是一个演员表,当然,它仍然是一个列表。
而不是-
List<A> listOfA = new List<C>(); // compiler Error
在问题的原始代码中,使用-
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
【讨论】:
在这种情况下如何使用 IEnumerable? 问题原代码中的List<A> listOfA = new List<C>(); // compiler Error
,输入IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
以 IEnumerable引用埃里克的精彩解释
会发生什么?您希望长颈鹿列表中包含一只老虎吗?你想崩溃吗?还是您希望编译器首先通过使分配非法来保护您免受崩溃? 我们选择后者。
但是,如果您想选择运行时崩溃而不是编译错误怎么办?你通常会使用 Cast 或 ConvertAll 但你会遇到两个问题: 它会创建列表的副本。如果您在新列表中添加或删除某些内容,这将不会反映在原始列表中。其次,由于它会使用现有对象创建一个新列表,因此会带来很大的性能和内存损失。
我遇到了同样的问题,因此我创建了一个包装类,它可以强制转换通用列表而无需创建全新的列表。
在原来的问题中你可以使用:
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);
【讨论】:
我刚刚将它用作 MVC 视图模型并获得了不错的通用剃须刀局部视图。惊人的想法!我很高兴能读到这篇文章。 我只会在类声明中添加一个“where TTo : TFrom”,这样编译器就可以警告不正确的使用。无论如何,制作一个不相关类型的 CastedList 是无意义的,制作一个“CastedList您只能转换为只读列表。例如:
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 现在有 object 元素 - 这应该是不可能的,但事实并非如此。
【讨论】:
这是我最容易理解的答案。特别是因为它提到有一个叫做 IReadOnlyList 的东西,它会起作用,因为它保证不会添加更多元素。 很遗憾你不能为 IReadOnlyDictionary这是 BigJim 出色的 answer 的扩展。
在我的例子中,我有一个带有 Children
字典的 NodeBase
类,我需要一种方法来一般地从孩子那里进行 O(1) 查找。我试图在 Children
的 getter 中返回一个私有字典字段,所以显然我想避免昂贵的复制/迭代。因此,我使用 Bigjim 的代码将 Dictionary<whatever specific type>
转换为通用 Dictionary<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>();
...
这很好。然而,我最终遇到了不相关的限制,并最终在基类中创建了一个抽象的FindChild()
方法来代替它进行查找。事实证明,这首先消除了对强制转换字典的需要。 (为了我的目的,我可以用一个简单的IEnumerable
替换它。)
所以您可能会问的问题(特别是如果性能是一个禁止您使用.Cast<>
或.ConvertAll<>
的问题)是:
“我真的需要转换整个集合,还是可以使用抽象方法来保存执行任务所需的特殊知识,从而避免直接访问集合?”
有时最简单的解决方案是最好的。
【讨论】:
【参考方案9】:至于为什么不起作用,了解covariance and contravariance可能会有所帮助。
只是为了说明为什么这个不应该工作,这里是对您提供的代码的更改:
void DoesThisWork()
List<C> DerivedList = new List<C>();
List<A> BaseList = DerivedList;
BaseList.Add(new B());
C FirstItem = DerivedList.First();
这应该有效吗?列表中的第一项是类型“B”,但 DerivedList 项的类型是 C。
现在,假设我们真的只想创建一个泛型函数,该函数对实现 A 的某种类型的列表进行操作,但我们不在乎那是什么类型:
void ThisWorks<T>(List<T> GenericList) where T:A
void Test()
ThisWorks(new List<B>());
ThisWorks(new List<C>());
【讨论】:
“这应该工作吗?” - 我愿意,也不会。我看不出为什么您不应该编写代码并使其在编译时失败,因为在您以“C”类型访问 FirstItem 时它实际上正在执行无效转换。有许多类似的方法来支持 C#。 如果您确实有充分的理由(并且有很多)想要实现此功能,那么下面 bigjim 的答案很棒。【参考方案10】:因为 C# 不允许那种类型的 inheritance 转换at the moment。
【讨论】:
首先,这是一个可转换性的问题,而不是继承的问题。其次,泛型类型的协变不适用于类类型,仅适用于接口和委托类型。 好吧,我很难和你争论。【参考方案11】:首先,停止使用难以理解的类名,如 A、B、C。使用 Animal、Mammal、Giraffe 或 Food、Fruit、Orange 或关系明确的东西。
那么您的问题是“为什么我不能将长颈鹿列表分配给动物列表类型的变量,因为我可以将长颈鹿分配给动物类型的变量?”
答案是:假设你可以。那么会出现什么问题呢?
好吧,您可以将老虎添加到动物列表中。假设我们允许您将长颈鹿列表放入包含动物列表的变量中。然后您尝试将老虎添加到该列表中。发生什么了?您希望长颈鹿列表中包含一只老虎吗?你想崩溃吗?还是您希望编译器首先通过使赋值非法来保护您免受崩溃?
我们选择后者。
这种转换称为“协变”转换。在 C# 4 中,我们将允许您在接口和委托上进行协变转换当转换被认为总是安全的时。有关详细信息,请参阅我关于协变和逆变的博客文章。 (本周星期一和星期四都会有关于这个主题的新内容。)
【讨论】:
实现非泛型 IList 的 IListIEnumerable
而不是List
进行这种转换?即:List<Animal> listAnimals = listGiraffes as List<Animal>;
是不可能的,但 IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
有效。
@jbueno:阅读我回答的最后一段。那里的转换已知是安全的。为什么?因为不可能把一个长颈鹿序列变成一个动物序列,然后把一只老虎放进动物序列。 IEnumerable<T>
和 IEnumerator<T>
都被标记为对协方差安全,并且编译器已经验证了这一点。
很长,但没有真正的论据。一串苹果就是一串水果。老虎动物园是动物的动物园。并且没有编译器可以使它不正确。【参考方案12】:
完成这项工作的方法是遍历列表并转换元素。这可以使用 ConvertAll 来完成:
List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
你也可以使用 Linq:
List<A> listOfA = new List<C>().Cast<A>().ToList();
【讨论】:
另一个选项:List listOfA = listOfC.ConvertAll(x => (A)x); 哪个更快?ConvertAll
或 Cast
?
这将创建列表的副本。如果您在新列表中添加或删除某些内容,这将不会反映在原始列表中。其次,由于它使用现有对象创建了一个新列表,因此存在很大的性能和内存损失。请参阅下面的答案以获取没有这些问题的解决方案。
对 modiX 的回答:ConvertAll 是 List以上是关于将 List<DerivedClass> 转换为 List<BaseClass>的主要内容,如果未能解决你的问题,请参考以下文章
C++:为啥我的 DerivedClass 的构造函数无法访问 BaseClass 的受保护字段?
C# 将 List<string> 添加到 List<List<string>> 数组
如何将 List<Object> 转换为 List<MyClass>