对实体类Id自增

我们首先需要明白

lambda 表达式  (item=>item.Id)
        ↓
Expression 树   →  检查是属性访问
        ↓
Expression.Assign → 拼出 “item.Id = index”
        ↓
Compile() → 生成机器码委托  Action<T,int>
        ↓
放进 ConcurrentDictionary 缓存,终身复用

在原生的Linq中,我们可以写

List<Student> Students=CreateStudent();
Students.Select((item, index) => { item.Id = index; return item; });

我们可以自定义一个

Students.SelectWithIndex(item=>item.Id);
  /// <summary>
  /// 对序列按索引赋值,指定成员表达式 item => item.Id
  /// </summary>
  public static IEnumerable<T> SelectWithIndex<T>(this IEnumerable<T> source, Expression<Func<T, int>> memberSelector)
  {
      if (source == null) throw new ArgumentNullException(nameof(source));
      if (memberSelector == null) throw new ArgumentNullException(nameof(memberSelector));

      // 编译一次 setter
      var setter = GetOrCreateSetter(memberSelector);

      return source.Select((item, idx) =>
      {
          setter(item, idx);
          return item;
      });
  }

  // 内部:把 Expression<item.Id> 编译成 Action<item,index>
  private static readonly ConcurrentDictionary<(Type, string), Delegate> _cache = new ConcurrentDictionary<(Type, string), Delegate>();

// ① 这是一个“编译并缓存”的工厂方法:输入 lambda (item=>item.Id) ,输出 Action<T,int> 委托
//    泛型 T 就是你要处理的模型类型,例如 MotionCalibrationModel
private static Action<T, int> GetOrCreateSetter<T>(Expression<Func<T, int>> selector)
{
    // ② 拿到 T 的运行时类型信息,后面要用它拼缓存键
    var type = typeof(T);

    // ③ 拼一个复合键:(类型, lambda 主体字符串)
    //    例如 (MotionCalibrationModel, "item.Id")  这样同一个类不同属性也能分开缓存
    var key = (type, selector.Body.ToString());

    // ④ ConcurrentDictionary 的 GetOrAdd 是线程安全的“获取或添加”
    //    如果缓存里已有这个键,直接返回已编译好的委托;否则执行 lambda 表达式编译逻辑
    return (Action<T, int>)_cache.GetOrAdd(key, _ =>
    {
        /* ⑤ 下面是“编译”过程,只做一次,以后复用 */

        // ⑥ 确保 lambda  body 是 item.Id  这种“取属性”形式
        if (!(selector.Body is MemberExpression mem) ||
            !(mem.Member is PropertyInfo prop) ||
            !prop.CanWrite ||                 // 必须有 set 访问器
            prop.PropertyType != typeof(int)) // 必须是 int
            throw new ArgumentException("Lambda 必须返回一个可写的 int 属性,如 item => item.Id");

        // ⑦ 手工构造两个参数表达式
        //    paramItem  代表“将来传进来的对象”
        //    paramIndex 代表“将来传进来的索引值”
        var paramItem = Expression.Parameter(type, "item");
        var paramIndex = Expression.Parameter(typeof(int), "index");

        // ⑧ 构造赋值表达式:  item.Id = index
        //    左侧是“取属性”,右侧是“索引值”
        var assign = Expression.Assign(Expression.Property(paramItem, prop), paramIndex);

        // ⑨ 把赋值表达式包成 Lambda :
        //    (item, index) => item.Id = index
        //    Compile() 之后变成真正的委托 Action<T,int>
        return Expression.Lambda<Action<T, int>>(assign, paramItem, paramIndex).Compile();
    });
}
posted @ 2025-09-20 15:09  孤沉  阅读(7)  评论(0)    收藏  举报