C# 通用排序方法中如何在索引循环内引用泛型对象的指定属性
核心实现方案
C#作为静态强类型语言,无法在代码中直接把字符串形式的propertyName作为对象的属性访问符使用,必须通过反射或表达式树实现动态绑定,且所有实现逻辑都可基于索引遍历完成,完全适配你要求的第i项与i+1项对比的场景。
前置设计修正
你当前的泛型方法存在冗余设计:nameof关键字的返回值为固定string类型,完全不需要为属性名单独定义TNameOfProperty泛型参数,方法签名可以简化为:
public static List<TObjectType> SortObjectList<TObjectType>(List<TObjectType> objectList, string propertyName) { // 逻辑实现 }
方案1:基于反射实现属性值获取(适合低频次调用场景)
通过.NET反射机制获取目标对象上指定名称的属性元数据,直接在索引遍历的循环中读取属性值:
- 方法入口先校验传入的属性名是否存在于目标类型的公共实例属性中,找不到则抛出明确的参数异常
- 所有可排序的基础类型(
int/string/decimal等)都内置实现了IComparable接口,拿到属性值后可直接调用CompareTo方法完成跨类型通用对比 - 遍历逻辑完全基于索引访问,不依赖foreach,和你预期的循环结构完全匹配
示例代码:
using System.Reflection; public static List<TObjectType> SortObjectList<TObjectType>(List<TObjectType> objectList, string propertyName) { // 边界校验 if (objectList == null || objectList.Count <= 1) return objectList; // 获取对应属性的元数据 var propInfo = typeof(TObjectType).GetProperty( propertyName, BindingFlags.Public | BindingFlags.Instance); if (propInfo == null) throw new ArgumentException($"类型{typeof(TObjectType)}中不存在名为{propertyName}的公共实例属性"); int listCount = objectList.Count; for (int i = 0; i < listCount - 1; i++) { // 按索引取当前项与下一项的对象实例 var currentItem = objectList[i]; var nextItem = objectList[i + 1]; // 反射读取两个实例的目标属性值,转成可对比的IComparable类型 IComparable currentValue = propInfo.GetValue(currentItem) as IComparable; IComparable nextValue = propInfo.GetValue(nextItem) as IComparable; // 通用对比逻辑,完全适配int/string等所有实现IComparable的属性类型 if (currentValue?.CompareTo(nextValue) > 0) { // 交换位置等排序逻辑 (objectList[i], objectList[i + 1]) = (objectList[i + 1], objectList[i]); } } return objectList; }
该实现和你当前的调用方式完全兼容:
SortObjectList(Customer.CollectionData(), nameof(Customer.Name)); SortObjectList(Customer.CollectionData(), nameof(Customer.Id));
方案2:基于表达式树缓存实现(适合高频调用场景,性能接近原生直接访问)
注意:纯反射实现虽然写起来简单,但频繁调用时性能会有明显损耗,高频场景优先用表达式树缓存方案。
该方案核心逻辑:
- 首次传入某个属性名时,通过表达式树动态生成
Func<TObjectType, object>的属性读取委托(比如读取Customer.Name的委托本质就是item => item.Name) - 把生成的委托按「类型+属性名」为key做静态缓存,后续相同属性名的读取直接命中缓存,不需要重复走反射逻辑
- 循环中直接调用委托获取属性值,执行效率比纯反射高5-10倍,遍历逻辑仍然完全基于索引访问
示例核心代码片段:
// 静态缓存存储属性读取委托 private static readonly ConcurrentDictionary<(Type, string), Func<object, object>> _propCache = new(); public static List<TObjectType> SortObjectList<TObjectType>(List<TObjectType> objectList, string propertyName) { // ... 边界校验逻辑同反射版本 // 从缓存取属性读取委托,没有就生成 var getPropValue = _propCache.GetOrAdd( (typeof(TObjectType), propertyName), key => { // 生成参数表达式:item => var itemParam = System.Linq.Expressions.Expression.Parameter(typeof(TObjectType)); // 生成属性访问表达式:item.PropertyName var propAccess = System.Linq.Expressions.Expression.Property(itemParam, propertyName); // 转成object类型适配通用委托签名 var convertExpr = System.Linq.Expressions.Expression.Convert(propAccess, typeof(object)); // 编译成可直接执行的委托 return System.Linq.Expressions.Expression.Lambda<Func<object, object>>(convertExpr, itemParam).Compile(); }); // 后续循环里直接调用委托拿值 for (int i = 0; i < listCount - 1; i++) { var currentValue = getPropValue(objectList[i]) as IComparable; var nextValue = getPropValue(objectList[i + 1]) as IComparable; // 对比逻辑和反射版本完全一致 } }
优化提示
- 如果你不需要自定义排序算法(比如手写冒泡、插入排序),可以直接复用.NET内置的
List<T>.Sort方法:该方法底层本身就是基于索引的快速排序实现,不是foreach遍历,只需要把上面生成的属性读取委托转成排序用的比较器即可,性能比手写循环排序更稳定。 - 实现时不需要为不同类型的属性单独写对比逻辑,所有实现
IComparable的类型都可以通用同一套对比规则。
内容的提问来源于stack exchange,提问作者user11679142
相关产品推荐
相关产品推荐

