找回密码
 立即注册
首页 业界区 业界 微软.net表达式编译居然有bug?

微软.net表达式编译居然有bug?

司空娅玲 2025-10-1 11:59:01
微软.net表达式编译问题困扰本人很久了,
为此我整理了以下case给大家分享
1. 可行性调研



  • 用表达式把对象转化为另一个类型的对象
  • 当一个类含有多个同类型属性时,把相同类型转化提取为公共方法
  • LambdaExpression可以用来定义复用的公共方法
  • 一切看起来都很完美,但是居然翻车了!!!
2. 示例说明

2.1 Customer多个属性包含Address

对应CustomerDTO多个属性包含AddressDTO
  1. public class Customer
  2. {
  3.     public string Name { get; set; }
  4.     public Address Address { get; set; }
  5.     public Address[] Addresses { get; set; }
  6.     public List AddressList { get; set; }
  7. }
  8. public class Address
  9. {
  10.     public string City { get; set; }
  11. }
复制代码
  1. public class CustomerDTO
  2. {
  3.     public string Name { get; set; }
  4.     public AddressDTO Address { get; set; }
  5.     public AddressDTO[] Addresses { get; set; }
  6.     public List AddressList { get; set; }
  7. }
  8. public class AddressDTO
  9. {
  10.     public string City { get; set; }
  11. }
复制代码
2.2 定义公共方法把Address转化为AddressDTO
  1. /// <summary>
  2. /// 转化 Address -> AddressDTO
  3. /// </summary>
  4. /// <returns></returns>
  5. public static Expression<Func> CreateAddressDTO()
  6. {
  7.     var sourceType = typeof(Address);
  8.     var destType = typeof(AddressDTO);
  9.     // Address source;
  10.     var source = Expression.Parameter(sourceType, "source");
  11.     // AddressDTO dest;
  12.     var dest = Expression.Parameter(destType, "dest");
  13.     var body = Expression.Block(
  14.         [dest],
  15.         // dest = new AddressDTO();
  16.         Expression.Assign(dest, Expression.New(destType)),
  17.         // dest.City = source.City;
  18.         Expression.Assign(Expression.Property(dest, "City"), Expression.Property(source, "City")),
  19.         // return dest;
  20.         dest
  21.     );
  22.     return Expression.Lambda<Func>(body, source);
  23. }
复制代码
2.3 调用公共方法
  1. /// <summary>
  2. /// 转化 Customer -> CustomerDTO
  3. /// </summary>
  4. /// <returns></returns>
  5. public static Expression<Func<Customer, CustomerDTO>> CreateCustomerDTO()
  6. {        
  7.     var customerType = typeof(Customer);
  8.     var dtoType = typeof(CustomerDTO);
  9.     // Customer customer;
  10.     var customer = Expression.Parameter(customerType, "customer");
  11.     // CustomerDTO dto;
  12.     var dto = Expression.Parameter(dtoType, "dto");
  13.     var addressDTOConvertFunc = CreateAddressDTO();
  14.     var body = Expression.Block(
  15.         [dto],
  16.         // dto = new AddressDTO();
  17.         Expression.Assign(dto, Expression.New(dtoType)),
  18.         // dto.Name = customer.Name;
  19.         Expression.Assign(Expression.Property(dto, "Name"), Expression.Property(customer, "Name")),
  20.         // dto.Address = addressDTOConvertFunc.Invoke(customer.Address);
  21.         ConvertAddress(addressDTOConvertFunc, customer, dto),
  22.         // dto.Addresses
  23.         ConvertAddresses(addressDTOConvertFunc, customer, dto),
  24.         // dto.AddressList
  25.         ConvertAddressList(addressDTOConvertFunc, customer, dto),
  26.         // return dto
  27.         dto
  28.     );
  29.     return Expression.Lambda<Func<Customer, CustomerDTO>>(body, customer);
  30. }
  31. /// <summary>
  32. /// 转化 customer.Address -> dto.Address
  33. /// </summary>
  34. /// <param name="addressDTOConvertFunc">共用方法</param>
  35. /// <param name="customer"></param>
  36. /// <param name="dto"></param>
  37. /// <returns></returns>
  38. public static Expression ConvertAddress(Expression<Func> addressDTOConvertFunc, ParameterExpression customer, ParameterExpression dto)
  39. {
  40.     // dto.Address = addressDTOConvertFunc.Invoke(customer.Address);
  41.     return Expression.Assign(Expression.Property(dto, "Address"), Expression.Invoke(addressDTOConvertFunc, Expression.Property(customer, "Address")));        
  42. }
  43. /// <summary>
  44. /// 转化 customer.Address -> dto.Address
  45. /// </summary>
  46. /// <param name="addressDTOConvertFunc">共用方法</param>
  47. /// <param name="customer"></param>
  48. /// <param name="dto"></param>
  49. /// <returns></returns>
  50. public static BlockExpression ConvertAddresses(Expression<Func> addressDTOConvertFunc, ParameterExpression customer, ParameterExpression dto)
  51. {
  52.     // Address[] addresses;
  53.     var addresses = Expression.Parameter(typeof(Address[]), "addresses");
  54.     // int length;
  55.     var length = Expression.Variable(typeof(int), "length");
  56.     // AddressDTO[] dtoList;
  57.     var dtoList = Expression.Parameter(typeof(AddressDTO[]), "dtoList");
  58.     //// Address item;
  59.     //var item = Expression.Parameter(typeof(Address), "item");
  60.     var forLabel = Expression.Label("forLabel");
  61.     // int i;
  62.     var i = Expression.Variable(typeof(int), "i");
  63.     return Expression.Block(
  64.         [addresses, dtoList, length, i],
  65.         // addresses = customer.Addresses;
  66.         Expression.Assign(addresses, Expression.Property(customer, "Addresses")),
  67.         // length = addresses.Length;
  68.         Expression.Assign(length, Expression.ArrayLength(addresses)),
  69.         // dtoList = new AddressDTO[length];
  70.         Expression.Assign(dtoList, Expression.NewArrayBounds(typeof(AddressDTO), length)),
  71.         // dto.Addresses = dtoList;
  72.         Expression.Assign(Expression.Property(dto, "Addresses"), dtoList),
  73.         Expression.Loop(
  74.             Expression.IfThenElse(
  75.                 // i < length
  76.                 Expression.LessThan(i, length),                    
  77.                 Expression.Block(
  78.                     // dtoList[i] = addressDTOConvertFunc.Invoke(addressList[i]);
  79.                     Expression.Assign(Expression.ArrayAccess(dtoList, i), Expression.Invoke(addressDTOConvertFunc, Expression.ArrayAccess(addresses, i))),
  80.                     // i++;
  81.                     Expression.PostIncrementAssign(i)
  82.                 ),
  83.                 Expression.Break(forLabel)
  84.             ),
  85.             forLabel)
  86.     );
  87. }
  88. /// <summary>
  89. /// 转化 customer.AddressList -> dto.AddressList
  90. /// </summary>
  91. /// <param name="addressDTOConvertFunc">共用方法</param>
  92. /// <param name="customer"></param>
  93. /// <param name="dto"></param>
  94. /// <returns></returns>
  95. public static BlockExpression ConvertAddressList(Expression<Func> addressDTOConvertFunc, ParameterExpression customer, ParameterExpression dto)
  96. {
  97.     // List addressList;
  98.     var addressList = Expression.Parameter(typeof(List), "addressList");
  99.     // List dtoList;
  100.     var dtoList = Expression.Parameter(typeof(List), "dtoList");
  101.     // int count;
  102.     var count = Expression.Variable(typeof(int), "count");
  103.     //// Address item;
  104.     //var item = Expression.Parameter(typeof(Address), "item");
  105.     var forLabel = Expression.Label("forLabel");
  106.     var i = Expression.Variable(typeof(int), "i");
  107.     return Expression.Block(
  108.         [addressList, dtoList, count, i],
  109.         // addressList = customer.AddressList;
  110.         Expression.Assign(addressList, Expression.Property(customer, "AddressList")),
  111.         // dtoList = new List();
  112.         Expression.Assign(dtoList, Expression.New(typeof(List))),
  113.         // dto.AddressList = dtoList;
  114.         Expression.Assign(Expression.Property(dto, "AddressList"), dtoList),
  115.         // addressCount = addressList.Count;
  116.         Expression.Assign(count, Expression.Property(addressList, "Count")),
  117.         Expression.Loop(
  118.             Expression.IfThenElse(
  119.                 // i < addressCount
  120.                 Expression.LessThan(i, count),
  121.                 // dtoList.Add(addressDTOConvertFunc.Invoke(addressList[i++]));
  122.                 Expression.Call(
  123.                     dtoList,
  124.                     typeof(List).GetMethod("Add")!,
  125.                     Expression.Invoke(addressDTOConvertFunc, Expression.MakeIndex(addressList, typeof(List).GetProperty("Item"), [Expression.PostIncrementAssign(i)]))),
  126.                 Expression.Break(forLabel)
  127.             ),
  128.             forLabel)
  129.     );
  130. }
复制代码
2.3.1 代码解读



  • CreateCustomerDTO转化Customer为CustomerDTO
  • ConvertAddress转化Customer.Address为CustomerDTO.Address调用了CreateAddressDTO
  • ConvertAddresses转化Customer.Addresses为CustomerDTO.Addresses调用了CreateAddressDTO
  • ConvertAddressList转化Customer.AddressList为CustomerDTO.AddressList调用了CreateAddressDTO
  • 以上看上去是不是很完美!!!
  • 但是马上就要翻车了...
2.4 测试一下
  1. var expression = CreateCustomerDTO();
  2. var func = expression.Compile();
  3. Customer _customer = new()
  4. {
  5.     Name = "jxj",
  6.     Address = new() { City = "gz" },
  7.     AddressList = [new() { City = "bj" }],
  8.     Addresses = [new() { City = "sh" }]
  9. };
  10. var dto = func(_customer);
  11. // {"Name":"jxj","Address":{"City":"gz"},"Addresses":[{"City":"sh"}],"AddressList":[]}
复制代码
2.4.1 请大家围观翻车现场



  • Address和Addresses转化成功了,但是AddressList转化失败了
  • 如果说LambdaExpression不能复用,为什么Address和Addresses共用LambdaExpression能成功
  • 而且如果删掉Addresses属性AddressList就能转化成功
2.5 交换ConvertAddresses和ConvertAddressList前后顺序再测试
  1. public static Expression<Func<Customer, CustomerDTO>> CreateCustomerDTO()
  2. {
  3.     var customerType = typeof(Customer);
  4.     var dtoType = typeof(CustomerDTO);
  5.     // Customer customer;
  6.     var customer = Expression.Parameter(customerType, "customer");
  7.     // CustomerDTO dto;
  8.     var dto = Expression.Parameter(dtoType, "dto");
  9.     var addressDTOConvertFunc = CreateAddressDTO();
  10.     var body = Expression.Block(
  11.         [dto],
  12.         // dto = new AddressDTO();
  13.         Expression.Assign(dto, Expression.New(dtoType)),
  14.         // dto.Name = customer.Name;
  15.         Expression.Assign(Expression.Property(dto, "Name"), Expression.Property(customer, "Name")),
  16.         // dto.Address = addressDTOConvertFunc.Invoke(customer.Address);
  17.         ConvertAddress(addressDTOConvertFunc, customer, dto),
  18.         // dto.AddressList
  19.         ConvertAddressList(addressDTOConvertFunc, customer, dto),
  20.         // dto.Addresses
  21.         ConvertAddresses(addressDTOConvertFunc, customer, dto),
  22.         // return dto
  23.         dto
  24.     );
  25.     return Expression.Lambda<Func<Customer, CustomerDTO>>(body, customer);
  26. }
复制代码
2.5.1 得到以下结果
  1. {"Name":"jxj","Address":{"City":"gz"},"Addresses":[null],"AddressList":[{"City":"bj"}]}
复制代码


  • 无论列表还是数组,谁在前成功!!!
  • 是不是有点无语了
2.6 换成FastExpressionCompiler再测试一下
  1. var expression = CreateCustomerDTO();
  2. var func = FastExpressionCompiler.ExpressionCompiler.CompileFast<Func<Customer, CustomerDTO>>(expression);
  3. Customer _customer = new()
  4. {
  5.     Name = "jxj",
  6.     Address = new() { City = "gz" },
  7.     AddressList = [new() { City = "bj" }],
  8.     Addresses = [new() { City = "sh" }]
  9. };
  10. var dto = func(_customer);
  11. // {"Name":"jxj","Address":{"City":"gz"},"Addresses":[{"City":"sh"}],"AddressList":[{"City":"bj"}]}
复制代码
换成FastExpressionCompiler全部成功,这是不是实锤是微软的bug
3. 附两个note对比示例



  • expression_sys.dib是微软转化失败示例
  • expression_fast.dib是FastExpressionCompiler转化成功示例
  • 大家可以下载本地执行
  • 用vscode打开就能执行(需要Jupyter Notebook插件)
现在很纠结是不是要换方案了,还是要依赖第三方FastExpressionCompiler ...
源码托管地址: https://github.com/donetsoftwork/MyEmit ,欢迎大家直接查看源码。
gitee同步更新:https://gitee.com/donetsoftwork/MyEmit
如果大家喜欢请动动您发财的小手手帮忙点一下Star。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册