C# 转换列表<;DerivedClass>;列出<;基类>;

C# 转换列表<;DerivedClass>;列出<;基类>;,c#,list,inheritance,collections,covariance,C#,List,Inheritance,Collections,Covariance,虽然我们可以从基类/接口继承,但为什么我们不能声明一个列表 使用相同的类/接口 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

虽然我们可以从基类/接口继承,但为什么我们不能声明一个
列表
使用相同的类/接口

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
    }
}
接口A
{ }
B类:A
{ }
丙类:乙类
{ }
课堂测试
{
静态void Main(字符串[]参数)
{
A=新的C();//确定
List listOfA=new List();//编译器错误
}
}

有办法吗?

因为C不允许这种类型的继承转换。

实现这一点的方法是迭代列表并强制转换元素。这可以使用ConvertAll完成:

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
List listOfA=new List().ConvertAll(x=>(A)x);
您还可以使用Linq:

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

首先,停止使用不可能理解的类名,如A、B、C。使用动物、哺乳动物、长颈鹿或食物、水果、橘子或关系明确的东西

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

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

嗯,你可以在动物列表中添加一只老虎。假设我们允许您将一个长颈鹿列表放入一个变量中,该变量包含一个动物列表。然后你试着在列表中添加一只老虎。会发生什么?你想让长颈鹿的名单上有一只老虎吗?你想撞车吗?或者,您希望编译器首先通过使赋值非法来保护您免受崩溃的影响吗

我们选择后者


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

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

为了说明为什么这不起作用,下面是对您提供的代码的更改:

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

     C FirstItem = DerivedList.First();
}
void完成这项工作()
{
List DerivedList=新列表();
List BaseList=衍生列表;
添加(新的B());
C FirstItem=DerivedList.First();
}
这样行吗?列表中的第一项为“B”类型,但派生列表项的类型为C

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

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

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}
void ThisWorks(列表GenericList),其中T:A
{
}
无效测试()
{
ThisWorks(新列表());
ThisWorks(新列表());
}

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

而不是-

List listOfA=new List();//编译器错误

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


IEnumerable listOfA=new List();//编译器错误-不再有!:)

引用Eric的精彩解释

会发生什么?你想让长颈鹿的名单上有一只老虎吗?你想撞车吗?或者,您希望编译器首先通过使赋值非法来保护您免受崩溃的影响吗? 我们选择后者

但是,如果要选择运行时崩溃而不是编译错误,该怎么办?您通常会使用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!
    }
}
类测试
{
静态void Main(字符串[]参数)
{
A=新的C();//确定
IList listOfA=new List().CastList();//现在可以了!
}
}
这里是包装类(+一个易于使用的扩展方法CastList)

公共类卡斯特列表:IList
{
公共IList基本清单;
公共卡斯特名单(IList基本名单)
{
基本列表=基本列表;
}
//数不清
IEnumerator IEnumerable.GetEnumerator(){return BaseList.GetEnumerator();}
//数不清
public IEnumerator GetEnumerator(){返回新的CastedEnumerator(BaseList.GetEnumerator());}
//I收集
公共整数计数{get{return BaseList.Count;}}
public bool IsReadOnly{get{return BaseList.IsReadOnly;}
公共无效添加(TTo项){BaseList.Add((TFrom)(object)项);}
public void Clear(){BaseList.Clear();}
public bool Contains(TTo项){返回BaseList.Contains((TFrom)(object)项);}
public void CopyTo(TTo[]数组,int arrayIndex){BaseList.CopyTo((TFrom[])(object)数组,arrayIndex);}
public bool Remove(TTo项){返回BaseList.Remove((TFrom)(object)项);}
//伊里斯特
此[整型索引]的公共目录
{
获取{返回(TTo)(对象)基列表[索引];}
设置{BaseList[index]=(TFrom)(对象)值;}
}
public int IndexOf(TTo项){return BaseList.IndexOf((TFrom)(object)项);}
public void Insert(int-index,TTo项){BaseList.Insert(index,(TFrom)(object)项);}
public void RemoveAt(int index){BaseList.RemoveAt(index);}
}
公共类CastedEnumerator:IEnumerator
{
公共IEnumerator基枚举器;
公共CastedEnumerator(IEnumerator baseEnumerator)
{
BaseEnumerator=BaseEnumerator;
}
//可识别
public void Dispose(){BaseEnumerator.Dispose();}
//电子计算器
对象IEnumerator.Current{get{return BaseEnumerator.Current;}
public bool MoveNext(){返回BaseEnumerator.MoveNext();}
public void Reset(){BaseEnumerator.Reset();}
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);
    }
}
// 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>();
    }
    ...
}
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());
public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}
using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);
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();
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;
}
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
}
  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
  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
  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!!!