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

微软.net表达式编译问题困扰本人很久了,
为此我整理了以下case给大家分享

1. 可行性调研

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

2. 示例说明

2.1 Customer多个属性包含Address

对应CustomerDTO多个属性包含AddressDTO

public class Customer {     public string Name { get; set; }     public Address Address { get; set; }     public Address[] Addresses { get; set; }     public List<Address> AddressList { get; set; } } public class Address {     public string City { get; set; } } 
public class CustomerDTO {     public string Name { get; set; }     public AddressDTO Address { get; set; }     public AddressDTO[] Addresses { get; set; }     public List<AddressDTO> AddressList { get; set; } } public class AddressDTO {     public string City { get; set; } } 

2.2 定义公共方法把Address转化为AddressDTO

/// <summary> /// 定义转化 Address -> AddressDTO /// </summary> /// <returns></returns> public static Expression<Func<Address, AddressDTO>> CreateAddressDTO() {     var sourceType = typeof(Address);     var destType = typeof(AddressDTO);     // Address source;     var source = Expression.Parameter(sourceType, "source");     // AddressDTO dest;     var dest = Expression.Parameter(destType, "dest");     var body = Expression.Block(         [dest],         // dest = new AddressDTO();         Expression.Assign(dest, Expression.New(destType)),         // dest.City = source.City;         Expression.Assign(Expression.Property(dest, "City"), Expression.Property(source, "City")),         // return dest;         dest     );     return Expression.Lambda<Func<Address, AddressDTO>>(body, source); } 

2.3 调用公共方法

/// <summary> /// 定义转化委托 Customer -> CustomerDTO /// </summary> /// <returns></returns> public static Expression<Func<Customer, CustomerDTO>> CreateCustomerDTO() {             var customerType = typeof(Customer);     var dtoType = typeof(CustomerDTO);     // Customer customer;     var customer = Expression.Parameter(customerType, "customer");     // CustomerDTO dto;     var dto = Expression.Parameter(dtoType, "dto");     // 可以复用的功能方法     var addressDTOConvertFunc = CreateAddressDTO();     var body = Expression.Block(         [dto],         // dto = new AddressDTO();         Expression.Assign(dto, Expression.New(dtoType)),         // dto.Name = customer.Name;         Expression.Assign(Expression.Property(dto, "Name"), Expression.Property(customer, "Name")),         // dto.Address = addressDTOConvertFunc.Invoke(customer.Address);         ConvertAddress(addressDTOConvertFunc, customer, dto),         // dto.Addresses         ConvertAddresses(addressDTOConvertFunc, customer, dto),         // dto.AddressList         ConvertAddressList(addressDTOConvertFunc, customer, dto),         // return dto         dto     );     return Expression.Lambda<Func<Customer, CustomerDTO>>(body, customer); } 

2.3.1 代码解读

  • CreateCustomerDTO转化Customer为CustomerDTO
  • ConvertAddress转化Customer.Address为CustomerDTO.Address调用了CreateAddressDTO
  • ConvertAddresses转化Customer.Addresses为CustomerDTO.Addresses调用了CreateAddressDTO
  • ConvertAddressList转化Customer.AddressList为CustomerDTO.AddressList调用了CreateAddressDTO
  • 以上ConvertAddress、ConvertAddresses和ConvertAddressList本文未贴代码,文末有提供git仓库,可以下载查看
  • 以上看上去是不是很完美!!!
  • 但是马上就要翻车了...

2.4 测试一下

var expression = CreateCustomerDTO(); var func = expression.Compile(); Customer _customer = new() {     Name = "jxj",     Address = new() { City = "gz" },     AddressList = [new() { City = "bj" }],     Addresses = [new() { City = "sh" }] }; var dto = func(_customer); // {"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前后顺序再测试

public static Expression<Func<Customer, CustomerDTO>> CreateCustomerDTO() {     var customerType = typeof(Customer);     var dtoType = typeof(CustomerDTO);     // Customer customer;     var customer = Expression.Parameter(customerType, "customer");     // CustomerDTO dto;     var dto = Expression.Parameter(dtoType, "dto");     var addressDTOConvertFunc = CreateAddressDTO();     var body = Expression.Block(         [dto],         // dto = new AddressDTO();         Expression.Assign(dto, Expression.New(dtoType)),         // dto.Name = customer.Name;         Expression.Assign(Expression.Property(dto, "Name"), Expression.Property(customer, "Name")),         // dto.Address = addressDTOConvertFunc.Invoke(customer.Address);         ConvertAddress(addressDTOConvertFunc, customer, dto),         // dto.AddressList         ConvertAddressList(addressDTOConvertFunc, customer, dto),         // dto.Addresses         ConvertAddresses(addressDTOConvertFunc, customer, dto),         // return dto         dto     );     return Expression.Lambda<Func<Customer, CustomerDTO>>(body, customer); } 

2.5.1 得到以下结果

{"Name":"jxj","Address":{"City":"gz"},"Addresses":[null],"AddressList":[{"City":"bj"}]} 
  • 无论列表还是数组,谁在前成功!!!
  • 是不是有点无语了

2.6 换成FastExpressionCompiler再测试一下

var expression = CreateCustomerDTO(); var func = FastExpressionCompiler.ExpressionCompiler.CompileFast<Func<Customer, CustomerDTO>>(expression); Customer _customer = new() {     Name = "jxj",     Address = new() { City = "gz" },     AddressList = [new() { City = "bj" }],     Addresses = [new() { City = "sh" }] }; var dto = func(_customer); // {"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。

发表评论

评论已关闭。

相关文章