C#遍历不同子类对象列表时访问子类属性报错如何解决
创建列表存放继承自基类QueryExecutor的不同子类实例,遍历列表读取queryprompt属性时抛出如下错误:
QueryExecutor does not contain a definition for queryprompt and not accessible extension method queryprompt accepting a first argument of type QueryExecutor could be found
报错根因:queryprompt等属性及相关业务方法仅在各个子类中单独定义,未在基类QueryExecutor中声明。
问题代码
public static List<Object> queries = new List<Object>(); #if DEBUG QueryExecutor.queries.Add(new TestString(url, program, key)); #endif QueryExecutor.queries.Add(new UATBlank(url, program, key)); QueryExecutor.queries.Add(new AcceptanceCriteriaBlank(url, program, key)); QueryExecutor.queries.Add(new BDDFormatInvalid(url, program, key)); QueryExecutor.queries.Add(new TargetElevationDateStale(url, program, key)); QueryExecutor.queries.Add(new TargetElevationDateMissing(url, program, key)); QueryExecutor.queries.Add(new MissedSLA(url, program, key)); QueryExecutor.queries.Add(new CapabilityIDBlank(url, program, key)); foreach (QueryExecutor qe in queries) { Console.WriteLine(qe.queryprompt); if (Console.ReadLine().ToUpper() == "Y") { Console.WriteLine(qe.queryprogress); var workItems = await qe.QueryOpenBugs("eDellPrograms", rt).ConfigureAwait(false); workItems = await qe.filterResults(workItems); await qe.PrintOpenBugsAsync(workItems, key, qe.querycomment, nameof(qe)); } }
待澄清疑问
- 在
foreach循环遍历的场景下,该如何正确执行类型转换? - 是否可以通过基类类型的变量直接获取对象对应的实际子类类型?
- 尝试直接对
foreach迭代变量qe做赋值转换时,抛出Cannot assign to qe because it is a foreach iteration variable错误,该如何处理?
最优修复方案(不需要类型转换)
这是典型的多态使用场景,不需要靠类型转换解决问题。直接把所有子类共有的queryprompt、queryprogress、querycomment属性,以及QueryOpenBugs、filterResults、PrintOpenBugsAsync方法,全部在基类QueryExecutor中声明为抽象(abstract)成员,所有子类重写对应实现即可。
同时把列表声明从List<Object>改成List<QueryExecutor>,因为存入的全是QueryExecutor的子类实例,没必要用Object类型增加额外拆箱开销。改完之后原有遍历逻辑不需要做任何转换就能正常运行,是最符合面向对象设计的写法。
针对三个疑问的直接答复
foreach场景下的类型转换写法
如果你暂时不改基类设计,不要在foreach声明里强转,在循环体内部用C#的模式匹配语法做安全转换即可,转换后用新声明的局部变量操作:// 先把列表类型改成List<QueryExecutor> public static List<QueryExecutor> queries = new List<QueryExecutor>(); foreach (var item in queries) { // 以TestString子类为例,匹配成功后ts就是转换好的子类实例 if (item is TestString ts) { Console.WriteLine(ts.queryprompt); // 后续逻辑直接用ts访问子类成员 } // 其他子类按相同逻辑判断匹配即可 }这种写法自带类型校验,不会抛出非法类型转换异常。
通过基类变量获取实际子类类型
可以,直接调用基类变量的GetType()方法就能拿到实例的真实运行时类型:Type realType = qe.GetType();拿到类型后可以做类型判断、反射调用成员,但反射性能差,业务逻辑里不推荐优先用这种方式。
foreach迭代变量无法赋值的问题
C#语法规定foreach的迭代变量是只读的,不允许在循环体内对其重新赋值,所以不要试图直接给qe赋值转换结果。按照上面的写法,在循环体内新声明局部变量存放转换后的实例即可,完全不影响逻辑执行。
内容的提问来源于stack exchange,提问作者Nathan Dehnel

