C# Record类型使用with表达式克隆时编译器生成额外引用的原因探究
with expression? Great question! Let's break down exactly what's happening here with record types and the with expression, and why that extra reference appears in the compiled code.
First, let's recap what the with expression does at a high level: it creates a copy (clone) of the original record instance, applies the specified property changes to that copy, and then returns the modified copy. This is the core of records' "non-destructive mutation" behavior.
Now let's look at your code and its compiled output step by step:
Original Code
public void M() { Point p1 = new Point(3, 3); Point p2 = p1 with { Y = 4 }; } record Point(int X,int Y);
Compiled (Decompiled) Code
public void M() { Point point = new Point(3, 3); Point point2 = point.<Clone>$(); point2.Y = 4; Point point3 = point2; }
Why the Extra Reference (point3 = point2)?
That extra assignment isn't redundant—it's a byproduct of how the compiler enforces the semantic guarantees of the with expression:
Isolate mutation to a temporary instance
The compiler first clones the original record intopoint2using the auto-generated<Clone>$()method (a hidden clone method all records receive). It then modifiespoint2.Yto 4. By doing this mutation on a temporary variable instead of directly on the finalp2variable, the compiler ensures thatp2will only ever hold a fully initialized, consistent instance. If something went wrong during the mutation (unlikely here, but a general safeguard),p2wouldn't end up with a partially modified record.Align with expression semantics
Thewithexpression is an expression, not a statement. In C#, expressions must evaluate to a single value without side effects leaking into the target variable prematurely. The compiler usespoint2as scratch space to perform the clone and mutation, then assigns the final result topoint3(which maps to your originalp2variable). This keeps the execution model consistent with how other complex expressions are handled in the language.Handle init-only properties correctly
Records auto-generateinit-only properties for primary constructor parameters (likeXandYin yourPointrecord). Normally, you can't modify aninitproperty after initialization—but the compiler makes an exception here forwithexpressions. The temporary variablepoint2lets the compiler bypassinitaccessor restrictions safely during the mutation step, before the final instance is assigned to your variable.
Why does this only happen with non-destructive mutation?
Direct record instantiation (like new Point(3,3)) doesn't require cloning or post-instantiation modification, so there's no need for a temporary variable. The with expression is the only scenario where the compiler needs to create a clone, modify it, and then pass it to the target variable—hence the extra reference only appears here.
内容的提问来源于stack exchange,提问作者Yossi Sternlicht

