惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
Schneier on Security
Schneier on Security
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
C
CERT Recently Published Vulnerability Notes
Security Latest
Security Latest
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
NISL@THU
NISL@THU
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
Webroot Blog
Webroot Blog
Project Zero
Project Zero
Google Online Security Blog
Google Online Security Blog
The Last Watchdog
The Last Watchdog
Spread Privacy
Spread Privacy
Hacker News: Ask HN
Hacker News: Ask HN
PCI Perspectives
PCI Perspectives
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
W
WeLiveSecurity
Attack and Defense Labs
Attack and Defense Labs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News | PayPal Newsroom
Help Net Security
Help Net Security
The Hacker News
The Hacker News
H
Heimdal Security Blog
O
OpenAI News
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
Cyberwarzone
Cyberwarzone
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 叶小钗
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
I
Intezer
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security Affairs
P
Proofpoint News Feed
S
Secure Thoughts
腾讯CDC
Google DeepMind News
Google DeepMind News
量子位
罗磊的独立博客

博客园 - xiangji

开源完美模块组件化可扩展的Xml解析器Hand.ParseXml Roslyn语法的模式匹配之EasySyntax增加模式匹配支持 SourceGenerator之扑风捉影 开源项目PocoEmit.Mapper重构之扑风捉影 .NET源码生成器基于partial范式开发和nuget打包 SourceGenerator之partial范式及测试 .NET源码生成器之SyntaxTree踩坑 .NET源码生成器使用SyntaxTree生成代码及简化语法 DBShadow.net之依赖注入 DBShadow.net之化繁为简 DBShadow.net之性能优化的坎坷路 DBShadow横空出世,Dapper.net的天花板盖不住了 Aspire+.NET10+手搓线程池打造抓不死的云应用 鸡肋的TaskFactory是时候抛弃了 重构《手搓》TaskFactory带你更安全的起飞 异步"伪线程"重构《手搓》线程池,支持任务清退 《手搓》线程池优化的追求 《手搓》TaskFactory带你安全的起飞 《手搓》线程池 致敬1024,《手搓》轻量级EventBus PocoEmit遥遥领先于AutoMapper之打通充血模型的任督二脉 PocoEmit遥遥领先于AutoMapper之循环引用 如何使用PocoEmit.Mapper替代AutoMapper
微软.net表达式编译居然有bug?
xiangji · 2025-09-06 · via 博客园 - xiangji

微软.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);
}
/// <summary>
/// 转化 customer.Address -> dto.Address
/// </summary>
/// <param name="addressDTOConvertFunc">共用方法</param>
/// <param name="customer"></param>
/// <param name="dto"></param>
/// <returns></returns>
public static Expression ConvertAddress(Expression<Func<Address, AddressDTO>> addressDTOConvertFunc, ParameterExpression customer, ParameterExpression dto)
{
    // dto.Address = addressDTOConvertFunc.Invoke(customer.Address);
    return Expression.Assign(Expression.Property(dto, "Address"), Expression.Invoke(addressDTOConvertFunc, Expression.Property(customer, "Address")));        
}
/// <summary>
/// 转化 customer.Address -> dto.Address
/// </summary>
/// <param name="addressDTOConvertFunc">共用方法</param>
/// <param name="customer"></param>
/// <param name="dto"></param>
/// <returns></returns>
public static BlockExpression ConvertAddresses(Expression<Func<Address, AddressDTO>> addressDTOConvertFunc, ParameterExpression customer, ParameterExpression dto)
{
    // Address[] addresses;
    var addresses = Expression.Parameter(typeof(Address[]), "addresses");
    // int length;
    var length = Expression.Variable(typeof(int), "length");
    // AddressDTO[] dtoList;
    var dtoList = Expression.Parameter(typeof(AddressDTO[]), "dtoList");
    //// Address item;
    //var item = Expression.Parameter(typeof(Address), "item");
    var forLabel = Expression.Label("forLabel");
    // int i;
    var i = Expression.Variable(typeof(int), "i");
    return Expression.Block(
        [addresses, dtoList, length, i],
        // addresses = customer.Addresses;
        Expression.Assign(addresses, Expression.Property(customer, "Addresses")),
        // length = addresses.Length;
        Expression.Assign(length, Expression.ArrayLength(addresses)),
        // dtoList = new AddressDTO[length];
        Expression.Assign(dtoList, Expression.NewArrayBounds(typeof(AddressDTO), length)),
        // dto.Addresses = dtoList;
        Expression.Assign(Expression.Property(dto, "Addresses"), dtoList),

        Expression.Loop(
            Expression.IfThenElse(
                // i < length
                Expression.LessThan(i, length),                    
                Expression.Block(
                    // dtoList[i] = addressDTOConvertFunc.Invoke(addressList[i]);
                    Expression.Assign(Expression.ArrayAccess(dtoList, i), Expression.Invoke(addressDTOConvertFunc, Expression.ArrayAccess(addresses, i))),
                    // i++;
                    Expression.PostIncrementAssign(i)
                ),
                Expression.Break(forLabel)
            ),
            forLabel)
    );
}
/// <summary>
/// 转化 customer.AddressList -> dto.AddressList
/// </summary>
/// <param name="addressDTOConvertFunc">共用方法</param>
/// <param name="customer"></param>
/// <param name="dto"></param>
/// <returns></returns>
public static BlockExpression ConvertAddressList(Expression<Func<Address, AddressDTO>> addressDTOConvertFunc, ParameterExpression customer, ParameterExpression dto)
{
    // List<Address> addressList;
    var addressList = Expression.Parameter(typeof(List<Address>), "addressList");

    // List<AddressDTO> dtoList;
    var dtoList = Expression.Parameter(typeof(List<AddressDTO>), "dtoList");
    // int count;
    var count = Expression.Variable(typeof(int), "count");
    //// Address item;
    //var item = Expression.Parameter(typeof(Address), "item");
    var forLabel = Expression.Label("forLabel");
    var i = Expression.Variable(typeof(int), "i");
    return Expression.Block(
        [addressList, dtoList, count, i],
        // addressList = customer.AddressList;
        Expression.Assign(addressList, Expression.Property(customer, "AddressList")),
        // dtoList = new List<AddressDTO>();
        Expression.Assign(dtoList, Expression.New(typeof(List<AddressDTO>))),
        // dto.AddressList = dtoList;
        Expression.Assign(Expression.Property(dto, "AddressList"), dtoList),
        // addressCount = addressList.Count;
        Expression.Assign(count, Expression.Property(addressList, "Count")),
        Expression.Loop(
            Expression.IfThenElse(
                // i < addressCount
                Expression.LessThan(i, count),
                // dtoList.Add(addressDTOConvertFunc.Invoke(addressList[i++]));
                Expression.Call(
                    dtoList, 
                    typeof(List<AddressDTO>).GetMethod("Add")!, 
                    Expression.Invoke(addressDTOConvertFunc, Expression.MakeIndex(addressList, typeof(List<Address>).GetProperty("Item"), [Expression.PostIncrementAssign(i)]))),
                Expression.Break(forLabel)
            ),
            forLabel)
    );
}

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 测试一下

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。