C#表达式中new Object()与new Object{}的区别及初始化影响
空对象初始化器大括号在表达式树中的关键影响
这个问题其实是踩了「常规C#代码等价,但表达式树结构不同」的坑,我来给你拆解清楚:
一、常规代码 vs 表达式树的核心差异
在普通的C#执行代码里,new TDest()和new TDest{}确实是完全等价的——都会调用类的无参构造函数,没有任何功能差异,所以ReSharper的RedundantEmptyObjectOrCollectionInitializer规则在普通代码里是完全安全的。
但表达式树是用来描述代码结构的抽象语法树,它会精准保留你写的语法细节,这时候两种写法就产生了关键差异:
new TDest{}会被编译成 MemberInitExpression 类型的表达式节点:这个节点包含两部分——一个创建对象的NewExpression(调用无参构造),加上一个空的成员初始化列表。new TDest()则直接编译成 NewExpression 类型的节点:仅仅表示调用无参构造函数,没有任何成员初始化的相关信息。
二、重构后代码失效的原因
你的代码大概率是依赖了表达式树的节点类型来做逻辑处理——比如某些对象映射库(像AutoMapper)、ORM框架(比如EF Core的查询表达式解析),或者你自己写的表达式树解析逻辑,会判断表达式节点是否是MemberInitExpression,然后执行对应的处理逻辑(哪怕初始化列表是空的)。当你改成new TDest()后,节点变成了NewExpression,原来的解析逻辑找不到预期的节点类型,就会抛出异常或者出现不符合预期的行为。
你可以用一段简单的代码验证这个差异:
// 定义测试类 public class TestSource {} public class TestDest {} // 原表达式 Expression<Func<TestSource, TestDest>> expr1 = model => new TestDest{}; Console.WriteLine(expr1.Body.GetType().Name); // 输出:MemberInitExpression // 重构后的表达式 Expression<Func<TestSource, TestDest>> expr2 = model => new TestDest(); Console.WriteLine(expr2.Body.GetType().Name); // 输出:NewExpression
三、总结
ReSharper的这个重构规则没有考虑表达式树的特殊场景,它默认两种写法语义等价,但在表达式树的语境下,空对象初始化器{}和直接调用构造函数()的语法结构会被记录成不同的节点类型,导致依赖节点类型的代码逻辑失效。所以在表达式树中,这两种写法并不是可互换的。
内容的提问来源于stack exchange,提问作者cSteusloff
相关产品推荐
相关产品推荐

