转换列表列出
虽然我们可以从基类/界面继承,但为什么我们不能声明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
}
}
有办法吗?
While we can inherit from base class/interface, why can't we declare a List<>
using same class/interface?
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
}
}
Is there a way around?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
首先,停止使用 A、B、C 等难以理解的类名。使用 Animal、Mammal、Giraffe、Food、Fruit、Orange 等关系明确的名称。
那么你的问题是“为什么我不能将长颈鹿列表分配给动物类型列表的变量,因为我可以将长颈鹿分配给动物类型的变量?”
答案是:假设你可以。那么会出现什么问题呢?
好吧,您可以将老虎添加到动物列表中。假设我们允许您将长颈鹿列表放入保存动物列表的变量中。然后你尝试将一只老虎添加到该列表中。会发生什么?您希望长颈鹿列表中包含老虎吗?你想要崩溃吗?或者您是否希望编译器通过首先将赋值设置为非法来保护您免受崩溃?
我们选择后者。
这种转换称为“协变”转换。在 C# 4 中,当已知转换始终安全时,我们将允许您在接口和委托上进行协变转换。有关详细信息,请参阅我关于协变和逆变的博客文章。 (本周的周一和周四都会有关于这个主题的新内容。)
First of all, stop using impossible-to-understand class names like A, B, C. Use Animal, Mammal, Giraffe, or Food, Fruit, Orange or something where the relationships are clear.
Your question then is "why can I not assign a list of giraffes to a variable of type list of animal, since I can assign a giraffe to a variable of type animal?"
The answer is: suppose you could. What could then go wrong?
Well, you can add a Tiger to a list of animals. Suppose we allow you to put a list of giraffes in a variable that holds a list of animals. Then you try to add a tiger to that list. What happens? Do you want the list of giraffes to contain a tiger? Do you want a crash? or do you want the compiler to protect you from the crash by making the assignment illegal in the first place?
We choose the latter.
This kind of conversion is called a "covariant" conversion. In C# 4 we will allow you to make covariant conversions on interfaces and delegates when the conversion is known to be always safe. See my blog articles on covariance and contravariance for details. (There will be a fresh one on this topic on both Monday and Thursday of this week.)
引用Eric的精彩解释
但是,如果您想选择运行时崩溃而不是编译错误怎么办?您通常会使用 Cast<>或ConvertAll<>但这样你会遇到两个问题:它将创建列表的副本。如果您在新列表中添加或删除某些内容,这不会反映在原始列表中。其次,由于它使用现有对象创建新列表,因此会带来很大的性能和内存损失。
我遇到了同样的问题,因此我创建了一个包装类,它可以投射通用列表,而无需创建全新的列表。
在最初的问题中,您可以使用:
这里是包装类(+一个扩展方法 CastList 以便于使用)
To quote the great explanation of Eric
But what if you want to choose for a runtime crash instead of a compile error? You would normally use Cast<> or ConvertAll<> but then you will have 2 problems: It will create a copy of the list. If you add or remove something in the new list, this won't be reflected in the original list. And secondly, there is a big performance and memory penalty since it creates a new list with the existing objects.
I had the same problem and therefore I created a wrapper class that can cast a generic list without creating an entirely new list.
In the original question you could then use:
and here the wrapper class (+ an extention method CastList for easy use)
如果你使用
IEnumerable
代替,它会起作用(至少在 C# 4.0 中,我没有尝试过以前的版本)。这只是一个演员表,当然,它仍然是一个列表。而不是 -
List listOfA = new List(); // 编译器错误
在问题的原始代码中,使用 -
IEnumerable; listOfA = new List(); // 编译器错误 - 不再有! :)
If you use
IEnumerable
instead, it will work (at least in C# 4.0, I have not tried previous versions). This is just a cast, of course, it will still be a list.Instead of -
List<A> listOfA = new List<C>(); // compiler Error
In the original code of the question, use -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
至于为什么它不起作用,理解协方差和逆变。
只是为了说明为什么这不应该工作,这里对您提供的代码进行了更改:
这应该工作吗?列表中的第一项是类型“B”,但 DerivedList 项的类型是 C。
现在,假设我们真的只想创建一个通用函数,该函数对实现 A 的某种类型的列表进行操作,但是我们不在乎那是什么类型:
As far as why it doesn't work, it might be helpful to understand covariance and contravariance.
Just to show why this shouldn't work, here is a change to the code you provided:
Should this work? The First item in the list is of Type "B", but the type of the DerivedList item is C.
Now, assume that we really just want to make a generic function that operates on a list of some type which implements A, but we don't care what type that is:
您只能施放阅读列表。例如:
您不能为支持保存元素的列表做到这一点。原因是:
现在什么?请记住,ListObject和ListString实际上是同一列表,因此ListString现在具有对象元素 - 不可能,也不是。
You can only cast to readonly lists. For example:
And you cannot do it for lists that support saving elements. The reason why is:
What now? Remember that listObject and listString are the same list actually, so listString now have object element - it shouldn't be possible and it's not.
对于您的问题,有几个本机 c#可能性:
Ireadonlylist
,ienumerable
=&gt; 无错误和typesafe。 您可能需要的东西。list&lt;&gt;
正确的方法=&gt; Free / not Typeafearray < / code> =&gt; typesafe/throws运行时错误
dynamic
或list&lt; object&gt;
=&gt; gt; 通用/note typeafe/thr thr runtime错误所有这些都可以很好地工作! 无需任何棘手的编程!
所有示例的接口和类定义:
这是每个解决方案的示例:
1。
IREADONLYLIST
,iEnumerable
:您可能需要列表&lt; c&gt;
分配给iEnumerable代码>或
ireadonlylist&lt; a&gt;
添加
或remove> remove
elements。iEnumerable
或IREADONLYLIST
非常便宜。只是通过另一个指针访问数据。附加
元素元素到IEnumerable或IreadonlyList,则可以创建一个新的iEnumerable
。使用此iEnumerable
不当会变得昂贵。由于无法添加任何元素,因此所有元素仍然是正确的类型:
2。使用
list&lt;&gt;
正确的方式:list&lt; a&gt; listofa = new()
将猫添加到应该是熊的动物列表中:
3。
数组
:创建bear []
数组,可以保证所有数组元素参考实例bear
。在数组中携带:
4。
动态
和list&lt; dynamic&gt;
:最通用的解决方案将您的列表分配给
dynamic
或使用list&lt; dynamic&gt;
:For your problem there are several native C# possibilities:
IReadOnlyList
,IEnumerable
=> error free and typesafe. What you probably need.List<>
the proper way => error free / not typesafeArray
=> typesafe / throws runtime errorsdynamic
orList<object>
=> universal / not typesafe / throws runtime errorsAll of them work well! There is no need for any tricky programming!
Interface and class definitions for all of the examples:
Here are examples for each of the solutions:
1.
IReadOnlyList
,IEnumerable
: What you probably needList<C>
to anIEnumerable<A>
orIReadOnlyList<A>
Add
orRemove
elements.IEnumerable
orIReadOnlyList
is very cheap. Just accessing your data through another pointer.Append
elements to IEnumerable or IReadOnlyList, then you create a newIEnumerable
. Using thisIEnumerable
improperly may become expensive.Since no elements can be added, all elements remain of correct type:
2. Use
List<>
the proper way:List<A> listOfA = new()
Adding a Cat to a List of Animals which are supposed to be Bears:
3.
Array
: Creating aBear[]
array, it is guaranteed that all array elements reference instances ofBear
.Bears in an array:
4.
dynamic
andList<dynamic>
: The most universal solutionAssign your list to
dynamic
or useList<dynamic>
:您还可以使用
System.Runtime.CompilerServices.Unsafe
NuGet 包创建对同一List
的引用:根据上面的示例,您可以访问现有的
使用
实例。将tools
变量锤击列表中的Tool
实例添加到列表中会引发ArrayTypeMismatchException
异常,因为tools
引用与hammers
相同的变量。You can also use the
System.Runtime.CompilerServices.Unsafe
NuGet package to create a reference to the sameList
:Given the sample above, you can access the existing
Hammer
instances in the list using thetools
variable. AddingTool
instances to the list throws anArrayTypeMismatchException
exception becausetools
references the same variable ashammers
.我个人喜欢创建具有扩展课程的Libs
I personally like to create libs with extensions to the classes
因为 C# 不允许这种类型的
继承转换目前。Because C# doesn't allow that type of
inheritanceconversion at the moment.这是Bigjim的Brilliant 答案的扩展。
就我而言,我有一个带有
儿童
字典的NodeBase
类,我需要一种方法来从孩子那里进行O(1)查找。我试图在儿童
的Getter中返回一个私人字典字段,因此显然我想避免昂贵的复制/迭代。因此,我使用BigJim的代码将字典&lt;到通用
:dictionary&lt; nodebase&gt;
的任何特定类型>这很好。但是,我最终遇到了无关的局限性,最终在基类中创建了一个摘要
findchild()
方法,可以进行查找。事实证明,这首先消除了对演员词典的需求。 (出于我的目的,我能够用简单的iEnumerable
替换。)因此,您可能会问的问题(尤其是如果绩效是禁止您使用
.cast&lt;&gt;&gt; 或
.convertall&lt;&gt;
)是:“我真的需要施放整个集合,还是可以使用抽象方法来保留执行任务所需的特殊知识,从而避免直接访问该系列?”
有时最简单的解决方案是最好的。
This is an extension to BigJim's brilliant answer.
In my case I had a
NodeBase
class with aChildren
dictionary, and I needed a way to generically do O(1) lookups from the children. I was attempting to return a private dictionary field in the getter ofChildren
, so obviously I wanted to avoid expensive copying/iterating. Therefore I used Bigjim's code to cast theDictionary<whatever specific type>
to a genericDictionary<NodeBase>
:This worked well. However, I eventually ran into unrelated limitations and ended up creating an abstract
FindChild()
method in the base class that would do the lookups instead. As it turned out this eliminated the need for the casted dictionary in the first place. (I was able to replace it with a simpleIEnumerable
for my purposes.)So the question you might ask (especially if performance is an issue prohibiting you from using
.Cast<>
or.ConvertAll<>
) is:"Do I really need to cast the entire collection, or can I use an abstract method to hold the special knowledge needed to perform the task and thereby avoid directly accessing the collection?"
Sometimes the simplest solution is the best.
我已经阅读了整篇文章,我只想指出我认为不一致的地方。
编译器阻止您使用列表进行赋值:
但是编译器对于数组来说完全没问题:
关于赋值是否已知是安全的的争论在这里分崩离析。我对数组所做的分配不安全。为了证明这一点,如果我继续这样做:
我会得到一个运行时异常“ArrayTypeMismatchException”。人们如何解释这一点?如果编译器真的想阻止我做一些愚蠢的事情,它应该阻止我进行数组赋值。
I've read this whole thread, and I just want to point out what seems like an inconsistency to me.
The compiler prevents you from doing the assignment with Lists:
But the compiler is perfectly fine with arrays:
The argument about whether the assignment is known to be safe falls apart here. The assignment I did with the array is not safe. To prove that, if I follow that up with this:
I get a runtime exception "ArrayTypeMismatchException". How does one explain this? If the compiler really wants to prevent me from doing something stupid, it should have prevented me from doing the array assignment.
可能迟到了。
转换为数组也可以完成工作。
May be late.
Conversion to Array can also do the job.
我发现的最快方法是复制内部数组。
这是我的测试中的30倍,比
list.cast&lt; x&gt;()。tolist()
,尤其是有很多元素。如果您使用不安全的代码等,可能会更快的解决方案。
The fastest way i found is to copy the internal array.
This is in my tests 30x faster than
list.Cast<X>().ToList()
, especially with lots of elements.There are probably faster solutions if you use unsafe code etc.
认为我会把它扔到这里,因为如果这会使我绊倒,通常是当我传递
ilist&lt; giraffe&gt;
到采用iList&lt; andial&gt; 。
如果您可以更改该方法,请使用约束的
使其通用。这将强制执行类型的安全性,以便您在方法调用期间不能将任何犀牛添加到列表中,稍后您可以再次使用Rhinos列表再次调用该方法。
到
Figure I'd throw this here, because if this trips me up, it's usually when I'm passing my
IList<Giraffe>
to a method that takes aIList<Animal>
.If you can change the method, make it generic with a
where
constraint. This will enforce type safety such that you can't add any rhinos to your list during the method call, and later you can call the method again with a whole list of rhinos.to
完成这项工作的方法是迭代列表并转换元素。这可以使用 ConvertAll 来完成:
您也可以使用 Linq:
The way to make this work is to iterate over the list and cast the elements. This can be done using ConvertAll:
You could also use Linq: