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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1817300/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 21:05:11  来源:igfitidea点击:

Convert List<DerivedClass> to List<BaseClass>

c#listinheritancecollectionscovariance

提问by Asad

While we can inherit from base class/interface, why can't we declare a List<>using same class/interface?

虽然我们可以从基类/接口继承,为什么我们不能声明一个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
    }
}

Is there a way around?

有办法吗?

采纳答案by Mark Byers

The way to make this work is to iterate over the list and cast the elements. This can be done using ConvertAll:

完成这项工作的方法是迭代列表并转换元素。这可以使用 ConvertAll 来完成:

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

You could also use Linq:

你也可以使用 Linq:

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

回答by Noon Silk

Because C# doesn't allow that type of inheritanceconversion at the moment.

因为 C# 不允许这种类型的 遗产此刻转换。

回答by Eric Lippert

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.

首先,停止使用像 A、B、C 这样难以理解的类名。使用 Animal、Mammal、Giraffe 或 Food、Fruit、Orange 或一些关系明确的东西。

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.)

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

回答by Chris Pitman

As far as why it doesn't work, it might be helpful to understand covariance and contravariance.

至于为什么它不起作用,理解covariance 和 contravariance可能会有所帮助。

Just to show why this shouldn'twork, here is a change to the code you provided:

只是为了说明为什么这不应该工作,这里是一个改变你所提供的代码:

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

     C FirstItem = DerivedList.First();
}

Should this work? The First item in the list is of Type "B", but the type of the DerivedList item is C.

这应该工作吗?列表中的第一项是类型“B”,但 DerivedList 项的类型是 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:

现在,假设我们真的只想创建一个泛型函数,该函数对实现 A 的某种类型的列表进行操作,但我们并不关心它是什么类型:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}

回答by PhistucK

If you use IEnumerableinstead, 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.

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

Instead of -

代替 -

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

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! :)

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

回答by Bigjim

To quote the great explanation of Eric

引用 Eric 的精彩解释

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.

发生什么了?您希望长颈鹿列表中包含老虎吗?你想崩溃吗?或者您是否希望编译器首先通过使分配非法来保护您免受崩溃?我们选择后者。

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.

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

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:

在原始问题中,您可以使用:

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

and here the wrapper class (+ an extention method CastList for easy use)

和这里的包装类(+一个扩展方法 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);
    }
}

回答by Zach

This is an extension to BigJim's brilliant answer.

这是 BigJim 的精彩回答的延伸。

In my case I had a NodeBaseclass with a Childrendictionary, 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 of Children, so obviously I wanted to avoid expensive copying/iterating. Therefore I used Bigjim's code to cast the Dictionary<whatever specific type>to a generic Dictionary<NodeBase>:

在我的例子中,我有一个NodeBaseChildren字典的类,我需要一种方法来从孩子那里进行 O(1) 查找。我试图在 的 getter 中返回一个私有字典字段Children,所以显然我想避免昂贵的复制/迭代。因此,我使用 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>();
    }
    ...
}

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 simple IEnumerablefor my purposes.)

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

So the question you might ask (especially if performance is an issue prohibiting you from using .Cast<>or .ConvertAll<>) is:

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

"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.

有时最简单的解决方案是最好的。

回答by Wojciech Miko?ajewicz

You can only cast to readonly lists. For example:

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

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

And you cannot do it for lists that support saving elements. The reason why is:

对于支持保存元素的列表,您不能这样做。原因是:

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

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.

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

回答by vikingfabian

I personally like to create libs with extensions to the classes

我个人喜欢创建带有类扩展的库

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}

回答by Drew

You can also use the System.Runtime.CompilerServices.UnsafeNuGet package to create a reference to the same List:

您还可以使用System.Runtime.CompilerServices.UnsafeNuGet 包来创建对相同的引用List

using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);

Given the sample above, you can access the existing Hammerinstances in the list using the toolsvariable. Adding Toolinstances to the list throws an ArrayTypeMismatchExceptionexception because toolsreferences the same variable as hammers.

鉴于上面的示例,您可以Hammer使用tools变量访问列表中的现有实例。将Tool实例添加到列表会引发ArrayTypeMismatchException异常,因为tools引用与hammers.