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

有别的办法吗?


当前回答

可能会迟到。

转换为数组也可以完成这项工作。

main()
{
   List<Camel> camels = new List<Camel>();
   Reproducton(camels.ToArray());
}


public void Reproducton(Animal[] animals)
{
    foreach(var animal in animals.ToList())
    {
       var baby = animal.Reproduce();
    }
}

其他回答

引用埃里克的伟大解释

会发生什么呢?你想在长颈鹿的名单中包含一只老虎吗?你想撞车吗?或者你希望编译器通过在第一时间使赋值非法来保护你不崩溃? 我们选择后者。

但是,如果希望选择运行时崩溃而不是编译错误,该怎么办呢?你通常会使用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);
    }
}

对于你的问题,有几种原生c#的可能性:

动态 数组 IReadOnlyList, IEnumerable 正确使用List<>。 它们都工作得很好!不需要任何复杂的编程!

下面是它们各自的例子:

1. 动态:最通用的解决方案

运行时类型检查 您放弃了编译器错误检查支持,所以要小心处理!如果您尝试添加错误类型的元素,则只会得到一个运行时错误! 您甚至可以分配不相关类的集合。

简单写动态listOfA = new List<C>();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的实例。

You can exchange elements, but you can't remove or add new elements. Trying to set a wrong type yields a runtime error. 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:

Assign your List<C> to an IEnumerable<A> or IReadOnlyList<A> Neither of them can be changed at runtime, i.e. you can't Add or Remove elements. Why should the compiler allow assigning your List<C> to a List<A> instead of IReadOnlyList<A> when adding an element will lead to an error anyway? 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>()

Define a List of your interface Assign instances of one derived class only - you didn't want to store other classes anyway, did you? 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!!!

如果你使用IEnumerable代替,它将工作(至少在c# 4.0中,我没有尝试过以前的版本)。这只是一个集合,当然,它仍然是一个列表。

而不是——

List<A> listOfA = new List<C>(); // 编译器错误

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

IEnumerable<A> listOfA = new List<C>();//编译错误-没有更多!:)

我已经阅读了整篇文章,我只是想指出对我来说似乎不一致的地方。

编译器阻止你对list执行赋值操作:

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”。如何解释这一点呢?如果编译器真的想阻止我做一些愚蠢的事情,它应该阻止我做数组赋值。

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

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元素,这应该是不可能的。