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

有别的办法吗?


当前回答

对于你的问题,有几种原生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!!!

其他回答

你也可以使用System.Runtime.CompilerServices.Unsafe NuGet包来创建同一个列表的引用:

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相同的变量。

可能会迟到。

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

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

至于为什么它不起作用,理解协方差和逆变可能会有所帮助。

只是为了说明为什么这不能工作,这里是对您提供的代码的更改:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new 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>());
}

对于你的问题,有几种原生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!!!

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