You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Record类型使用with表达式克隆时编译器生成额外引用的原因探究

Why does the C# compiler generate an extra reference when using a record's 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 into point2 using the auto-generated <Clone>$() method (a hidden clone method all records receive). It then modifies point2.Y to 4. By doing this mutation on a temporary variable instead of directly on the final p2 variable, the compiler ensures that p2 will only ever hold a fully initialized, consistent instance. If something went wrong during the mutation (unlikely here, but a general safeguard), p2 wouldn't end up with a partially modified record.

  • Align with expression semantics
    The with expression 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 uses point2 as scratch space to perform the clone and mutation, then assigns the final result to point3 (which maps to your original p2 variable). This keeps the execution model consistent with how other complex expressions are handled in the language.

  • Handle init-only properties correctly
    Records auto-generate init-only properties for primary constructor parameters (like X and Y in your Point record). Normally, you can't modify an init property after initialization—but the compiler makes an exception here for with expressions. The temporary variable point2 lets the compiler bypass init accessor 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 16:12:38