虽然我们可以从基类/接口继承,但为什么我们不能声明 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。使用动物,哺乳动物,长颈鹿或食物,水果,橙色或其他关系清晰的东西。

你的问题是“为什么我不能将长颈鹿列表分配给动物类型变量的变量,因为我可以将长颈鹿分配给动物类型的变量?”

答案是:假设你可以。什么可能会出错?

好吧,您可以将Tiger添加到动物列表中。假设我们允许您将长颈鹿列表放在一个包含动物列表的变量中。然后你尝试将老虎添加到该列表中。怎么了?你想要长颈鹿的名单包含一只老虎吗?你想要崩溃吗?或者您是否希望编译器通过首先使分配非法来保护您免受崩溃?

我们选择后者。

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

引用Eric的精彩解释

  

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

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

我遇到了同样的问题,因此我创建了一个包装类,可以在不创建全新列表的情况下转换通用列表。

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

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();
}

这应该有用吗?列表中的第一项是类型“B”,但DerivedList项的类型是C.

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

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

}

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

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

而不是 -

<代码>列表与LT a取代; listOfA = new List&lt; C&gt;(); //编译器错误

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

<代码>的IEnumerable&LT a取代; listOfA = new List&lt; C&gt;(); //编译错误 - 没有更多! :)

您只能投射到只读列表。例如:

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现在具有对象元素 - 它应该是不可能的,而不是。

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

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

因为C#不允许这种类型的继承转换目前

这是BigJim精彩的答案的扩展。

在我的情况下,我有一个带有 Children 字典的 NodeBase 类,我需要一种方法来一般地从孩子那里进行O(1)查找。我试图在 Children 的getter中返回一个私有字典字段,所以显然我想避免昂贵的复制/迭代。因此,我使用Bigjim的代码将 Dictionary&lt;任何特定类型&gt; 转换为泛型 Dictionary&lt; NodeBase&gt;

// 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&lt;&gt; .ConvertAll&lt;&gt; )是:

“我真的需要整个集合,或者我可以使用抽象方法来保存执行任务所需的特殊知识,从而避免直接访问集合吗?”

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

您还可以使用 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 相同的变量。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top