提问人:user2401856 提问时间:12/16/2022 更新时间:12/16/2022 访问量:185
List<Parent> 持有 Child 元素而不丢失属性 C#
List<Parent> holding Child element without losing properties C#
问:
我有这些课程
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# 中使用泛型,这是可能的:list
Start
B
C
B
C
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
答:
这两个代码段之间的区别在于,在第一个代码段中,您没有访问任何特定于类型的信息(字段/属性/方法),即类似以下内容的内容也不会编译:
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/cast:Base
Base
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”:ToString
Base
B
A
Console.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)"
因此,您可以引入一些允许您使用的方法(如果不了解实际用例,很难分辨)。Base
List<Base>
该示例是可能的,因为 both 和 inherit from ,它提供了一个在写入输出的行上隐式调用的方法。也就是说,在该示例中,不使用 or 类型的成员,这些成员特定于它们自己的类型。List<Object>
int
string
Object
ToString()
int
string
您可以通过添加一个 both 和 可以实现的接口来完成没有泛型的需要,因为 和 属性是兼容的(它们都是 s)。然而,这显然是一个人为的例子,我预计真正的代码会更复杂。在这种情况下,泛型可能是您的最佳选择。B
C
b
c
int
因为所有 Base 对象都没有“b”字段
您需要测试 list[0] 是否是 'B' 的实例,然后将其转换为 B
if (list[0] is B )
{
Console.WriteLine(((B)(list[0]).b);
}
根据问题下面的注释,也许在这种情况下,非泛型接口和泛型类的组合可以工作。Start
泛型类的非泛型基接口会将 get-only 属性声明为 。IReadOnlyList 是协变量,允许返回不同的 List<T> 实例,其中 T 是 的具体派生类型。Start
List
IReadOnlyList<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 实例。
上一个:如何将父类强制转换为子类
下一个:使用现有父类的值实例化子类
评论