List<Parent> 持有 Child 元素而不丢失属性 C#

List<Parent> holding Child element without losing properties C#

提问人:user2401856 提问时间:12/16/2022 更新时间:12/16/2022 访问量:185

问:

我有这些课程

class Start
{
    public List<Base> list { get; set; }
    public Start() 
    {
        list = new List<Base>();
    }
}

public abstract class Base
{
    public int a { get; set; }
}

class B : Base
{
    public int b;
    public B(int a, int b) { this.a = a; this.b = b; }
}

class C : Base
{
    public int c;
    public C(int a, int c) { this.a = a; this.c = c; }
}

我希望类的属性保存类的实例或类的实例(不是两者在一起,但它可以保存任何或的相同类型)
如果可能的话,我不想
在 C# 中使用泛型,这是可能的:
listStartBCBC

List<Object> lst = new List<Object>();
lst.Add(1);
list.Add("Text");
Console.WriteLine("{0} {1}", lst[0], lst[1]);

我不明白为什么我不能在这里做出类似的行为:

Start s = new Start();
B b = new B(1, 2);
s.list.Add(b);
Console.WriteLine(s.list[0].a); //works
Console.WriteLine(s.list[0].b); //doesn't work
C# 继承 类型 强制转换

评论

0赞 12/16/2022
我想知道,你不想使用泛型有什么具体的原因,或者这只是个人对泛型的主观厌恶或不熟悉?
0赞 user2401856 12/16/2022
@MySkullCaveIsADarkPlace我正在使用两个 API,每个响应都有自己不同的文件,但有一个字段与两者共享,并且我为两者共享了多个常规属性(例如请求状态、请求消息和其他一些东西)
0赞 user2401856 12/16/2022
@MySkullCaveIsADarkPlace 在少数函数中,我不知道我会调用两个 API 中的哪一个,直到一些实时的事情发生,所以我不知道哪个 API 的结果会回到我身上,我需要处理这两个 API 并分别处理每种类型
0赞 12/16/2022
您可能可以以一种允许您这样做的方式使用泛型,同时也无法将错误的对象类型添加到 Start 实例中的 List<Base>。让我来回答一下......

答:

2赞 Guru Stron 12/16/2022 #1

这两个代码段之间的区别在于,在第一个代码段中,您没有访问任何特定于类型的信息(字段/属性/方法),即类似以下内容的内容也不会编译:

List<Object> lst = new List<Object>();
lst.Add(1);
list.Add("Text");
// will not compile despite string having Length property:
Console.WriteLine("{0} {1}", lst[0], lst[1].Length); 

a是类中声明的公共属性,因此它可用于 的每个子项,如果要访问子项特定的属性,则需要键入 test/castBaseBase

Start s = new Start();
B b = new B(1, 2);
s.list.Add(b);
Console.WriteLine(s.list[0].a); //works
if(s.list[0] is B b)
{
    Console.WriteLine(b.b);
}

或设为通用:Start

class Start<T> where T: Base
{
    public List<T> list { get; set; }
    public Start() 
    {
        list = new List<T>();
    }
}

var s = new Start<B>();
s.list.Add(new B(1, 2));
Console.WriteLine(s.list[0].b);

附言

请注意,在 中覆盖 ,并将使 “work”:ToStringBaseBAConsole.WriteLine("{0}", s.list[0]);

class B : Base
{
    // ...
    public override string ToString() => return $"B(A: {a} B: {b})";
}

class C : Base
{
    // ...
    public override string ToString() => return $"C(A: {a} B: {c})";
}

Start s = new Start();
B b = new B(1, 2);
s.list.Add(b);
s.list.Add(new C(4, 2));
Console.WriteLine("{0} {1}", s.list[0], s.list[1]); // prints "B(A: 1 B: 2) C(A: 4 B: 2)"

因此,您可以引入一些允许您使用的方法(如果不了解实际用例,很难分辨)。BaseList<Base>

1赞 Joel Coehoorn 12/16/2022 #2

该示例是可能的,因为 both 和 inherit from ,它提供了一个在写入输出的行上隐式调用的方法。也就是说,在该示例中,不使用 or 类型的成员,这些成员特定于它们自己的类型。List<Object>intstringObjectToString()intstring

您可以通过添加一个 both 和 可以实现的接口来完成没有泛型的需要,因为 和 属性是兼容的(它们都是 s)。然而,这显然是一个人为的例子,我预计真正的代码会更复杂。在这种情况下,泛型可能是您的最佳选择。BCbcint

1赞 pm100 12/16/2022 #3

因为所有 Base 对象都没有“b”字段

您需要测试 list[0] 是否是 'B' 的实例,然后将其转换为 B

if (list[0] is B )
{
       Console.WriteLine(((B)(list[0]).b);  
}
0赞 user19858830 12/16/2022 #4

根据问题下面的注释,也许在这种情况下,非泛型接口和泛型类的组合可以工作。Start

泛型类的非泛型基接口会将 get-only 属性声明为 。IReadOnlyList 是协变量,允许返回不同的 List<T> 实例,其中 T 是 的具体派生类型。StartListIReadOnlyList<Base>Base

public interface IStart
{
    IReadOnlyList<Base> List { get; }
}

泛型类实现 IStart,将 IStart.List 属性放在显式接口声明中,并声明其自己的 List 属性,该属性类型化为 List<TBase>Start<TBase>

public class Start<TBase> : IStart where TBase : Base
{
    public List<TBase> List { get; set; }

    IReadOnlyList<Base> IStart.List => this.List;

    public Start() 
    {
        List = new List<TBase>();
    }
}

请注意,IStart.List 的显式接口实现和 Start<TBase> 自己的 List 属性都返回相同的 List<TBase> 实例。

此设置使以下事情成为可能(或不可能,请参阅代码注释):

var startC = new Start<C>();

startC.List.Add(new C()); // this works, of course it works

startC.List.Add(new B()); // The compiler will NOT allow this


IStart SomeMethodProducingAStart()
{
   if (someCondition)
       return new Start<B>();
   else
       return new Start<C>();
}

void SomeMethodConsumingAStart(IStart start)
{
    if (start is Start<B> startWithBs)
    {
       // do stuff with startWithBs...
       Console.WriteLine(startWithBs.List[0].a);
       Console.WriteLine(startWithBs.List[0].b);
    }
    else if (start is Start<C> startWithCs)
    {
       // do stuff with startWithCs...
       Console.WriteLine(startWithCs.List[0].a);
       Console.WriteLine(startWithCs.List[0].c);
    }

    // if you don't care about the members specific to either B or C,
    // just do this
    Console.WriteLine(start.List[0].a);

    // since start can be any Start<T>
    // the following is denied by the compiler
    // simply by virtue of IStart.List being an IReadOnlyList
    start.List.Add(new C());    // no can do!
}


此方法是否适合您的应用程序方案由您确定,但这种方法试图避免对单个列表项进行精细的模式匹配,并旨在简化在模式匹配/强制转换为正确的 Start<TBase> 类型后使用 Start 实例。