泛型类中属性的编译期访问:替代dynamic与反射的方案
编译期解析的泛型类型属性访问方案
问题背景
现有DepartmentBase<T>泛型类的AddEmployee方法中,使用dynamic关键字便捷赋值T的属性,同时通过反射结合Linq筛选修改属性。这两种方式均为运行时解析,存在性能损耗,需要一种既保留dynamic般便捷写法、支持Linq操作,又能在编译期完成所有逻辑解析的方案。
原代码如下:
public class DepartmentBase<T> : MyBase where T : new() { public ObservableCollection<T> Employees { get { return _employees; } set { _employees = value; OnPropertyChanged(); } } private ObservableCollection<T> _employees; public T Selected_employee { get { return _selected_employee; } set { _selected_employee = value; OnPropertyChanged(); } } private T _selected_employee; public void AddEmployee(object parameter) { //Using dynamic - each type T has those 3 properties dynamic new_employee = new T(); new_employee.NEW = true; new_employee.START_DATE = DateTime.Now; Employees.Add(new_employee); //Using reflection with Read() and Set() methods, to set each T item property "NEW" to false Employees.Where(a => (bool)a.Read("NEW") == true).ToList().ForEach(b => b.Set("NEW", false)); Selected_employee = Employees.Last(); } }
解决方案一:接口约束(优先推荐)
核心思路是通过接口约束让泛型参数T必须实现包含所需属性的接口,使编译器在编译期就能确认T的属性成员,从而实现强类型访问,完全避免运行时解析。
步骤1:定义包含目标属性的接口
public interface IEmployee { bool NEW { get; set; } DateTime START_DATE { get; set; } }
步骤2:修改泛型类的约束条件
让T同时满足new()和IEmployee接口约束:
public class DepartmentBase<T> : MyBase where T : IEmployee, new()
步骤3:重构AddEmployee方法
直接以强类型方式访问属性,Linq操作也基于编译期确认的属性:
public void AddEmployee(object parameter) { var new_employee = new T(); new_employee.NEW = true; new_employee.START_DATE = DateTime.Now; Employees.Add(new_employee); // 直接通过强类型属性筛选并修改,编译期完成解析 foreach (var emp in Employees.Where(a => a.NEW)) { emp.NEW = false; } Selected_employee = Employees.Last(); }
方案优势
- 编译期安全:编译器会直接检查
T是否实现接口,若缺失属性会在编译阶段报错,避免运行时异常。 - 性能无损耗:所有属性访问和Linq操作均为编译期绑定,性能与直接操作具体类型一致。
- 写法简洁:和
dynamic写法一样直观,同时保留强类型的可读性与可维护性。
解决方案二:委托缓存(适配无法修改T的场景)
如果T是第三方类型或无法直接修改其代码实现接口,可通过编译委托缓存的方式,在程序启动时一次性生成属性访问的委托,后续调用直接使用编译后的委托,性能接近强类型访问。
重构后的DepartmentBase<T>类
public class DepartmentBase<T> : MyBase where T : new() { // 缓存属性访问委托,静态构造函数中仅初始化一次 private static readonly Func<T, bool> _getNew; private static readonly Action<T, bool> _setNew; private static readonly Action<T, DateTime> _setStartDate; static DepartmentBase() { // 反射获取属性,并编译为委托 var newProperty = typeof(T).GetProperty(nameof(NEW)) ?? throw new InvalidOperationException("类型T必须包含'NEW'布尔属性"); var startDateProperty = typeof(T).GetProperty(nameof(START_DATE)) ?? throw new InvalidOperationException("类型T必须包含'START_DATE'DateTime属性"); _getNew = (Func<T, bool>)Delegate.CreateDelegate(typeof(Func<T, bool>), newProperty.GetMethod); _setNew = (Action<T, bool>)Delegate.CreateDelegate(typeof(Action<T, bool>), newProperty.SetMethod); _setStartDate = (Action<T, DateTime>)Delegate.CreateDelegate(typeof(Action<T, DateTime>), startDateProperty.SetMethod); } public ObservableCollection<T> Employees { get { return _employees; } set { _employees = value; OnPropertyChanged(); } } private ObservableCollection<T> _employees; public T Selected_employee { get { return _selected_employee; } set { _selected_employee = value; OnPropertyChanged(); } } private T _selected_employee; public void AddEmployee(object parameter) { var new_employee = new T(); // 通过委托访问属性,无运行时解析开销 _setNew(new_employee, true); _setStartDate(new_employee, DateTime.Now); Employees.Add(new_employee); // Linq结合委托筛选,编译期完成逻辑解析 foreach (var emp in Employees.Where(emp => _getNew(emp))) { _setNew(emp, false); } Selected_employee = Employees.Last(); } }
方案优势
- 低性能损耗:委托仅在静态构造函数中生成一次,后续调用为直接执行编译后的方法,性能接近强类型访问。
- 无侵入性:不需要修改
T的代码,适配第三方类型或无法修改的现有类型。 - 编译期检查:静态构造函数中会提前验证属性是否存在,若缺失会在程序启动时抛出异常,避免运行时随机错误。
内容的提问来源于stack exchange,提问作者Lucy82
相关产品推荐
相关产品推荐

