C# 如何获取泛型实例List属性类型并动态调用对应泛型方法
问题描述
定义泛型方法 methodA,其泛型参数为T,所有可作为T传入的类(如C1、C2等)都包含一个List类型属性,不同类的该属性名称、列表元素类型(记为L)均不相同,类定义示例如下:
public class C1{ public List<L1> C1List{get;set;}=new List<L1>(); } public class C2{ public List<L2> C2List {get;set;}=new List<L2>(); }
需要在methodA内部调用另一个泛型参数为TX的方法 methodB,要求调用methodB时传入的泛型参数TX为前述List属性的元素类型L。
目前尝试通过反射获取T实例的List类型属性,但无法确定该属性对应的列表元素类型作为methodB的泛型参数(即代码中TypeOfPi的取值无法确定),未完成的实现代码如下:
public T methodA<T>(){ var r= Activator.CreateInstance<T>(); var pi= r.GetType().GetProperties().First(p => p.PropertyType.Name == "List`1"); pi.SetValue(r,methodB<TypeOfPi>()); return r; } public List<TX> methodB<TX>(){ var r=new List<TX>(); // 内部逻辑省略 return r; }
需要找到获取TypeOfPi取值的方案,或其他可行实现方式。
实现方案
泛型参数在编译时确定的场景下可以直接写在尖括号中,当前场景List元素类型是运行时通过反射拿到的Type对象,无法直接写入泛型参数位置,需要通过反射动态构造泛型方法调用,具体实现步骤如下:
- 从找到的List属性类型中,通过
GetGenericArguments()方法取第一个泛型参数,就是List的元素类型(即需要的TypeOfPi) - 先获取
methodB对应的开放泛型方法定义(未指定泛型参数的原始方法) - 用拿到的元素类型作为泛型参数,通过
MakeGenericMethod构造出可用的闭合泛型方法 - 调用闭合泛型方法拿到返回值,赋值给对应属性
修正后的反射实现代码如下:
// 给T加new()约束,替代Activator.CreateInstance,编译时可检查无参构造,性能更好 public T methodA<T>() where T : new() { var r = new T(); // 优化List属性判断逻辑,比直接判断类型名更严谨,避免匹配到自定义同名非泛型类型 var pi = r.GetType().GetProperties() .First(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(List<>)); // 获取List属性的元素类型,即需要的TypeOfPi Type listElementType = pi.PropertyType.GetGenericArguments()[0]; // 取methodB的开放泛型方法定义,替换下面的YourClass为当前方法所在的类名 // 如果methodB是静态方法,把BindingFlags.Instance改成BindingFlags.Static var openMethodB = typeof(YourClass) .GetMethod(nameof(methodB), BindingFlags.Public | BindingFlags.Instance); // 构造指定元素类型的闭合泛型方法 var closedMethodB = openMethodB.MakeGenericMethod(listElementType); // 调用方法:实例方法第一个参数传当前实例this,静态方法传null var methodResult = closedMethodB.Invoke(this, null); pi.SetValue(r, methodResult); return r; }
如果可以修改传入的C1、C2等类的定义,可以通过接口约束实现无反射的类型安全方案,性能更高:
// 定义统一泛型接口 public interface IListContainer<TItem> { List<TItem> ContainerList { get; set; } } // 所有传入T的类实现该接口,显式实现不需要修改原有属性名 public class C1 : IListContainer<L1> { public List<L1> C1List{get;set;}=new List<L1>(); List<L1> IListContainer<L1>.ContainerList { get => C1List; set => C1List = value; } } public class C2 : IListContainer<L2> { public List<L2> C2List {get;set;}=new List<L2>(); List<L2> IListContainer<L2>.ContainerList { get => C2List; set => C2List = value; } } // 改写methodA,增加泛型参数,不需要反射,编译时做类型检查 public T methodA<T, TItem>() where T : IListContainer<TItem>, new() { var r = new T(); r.ContainerList = methodB<TItem>(); return r; }
注意:反射方案需要确保每个传入的T类型有且仅有一个
List<>类型属性,否则会抛出异常或匹配到错误属性;接口方案需要修改实体类定义,可根据实际场景选择。
内容的提问来源于stack exchange,提问作者aza
相关产品推荐
相关产品推荐

