c# Primary Constructors 和 record 在并发数据模型中的应用

来源:这里教程网 时间:2026-02-21 17:39:15 作者:

Primary Constructors 不能直接用于并发安全的可变状态封装

Primary Constructors 是 C# 12 引入的语法糖,它让构造逻辑更紧凑,但本身不提供线程安全保证。如果你写

public class Counter(int initial = 0) { public int Value = initial; }
Value
是公开字段,多个线程同时读写会引发竞态——编译器不会自动加锁或转为
volatile

常见错误现象:在高并发计数场景中,

counter.Value++
看似原子,实则被拆成“读-改-写”三步,结果远小于预期值。

Primary Constructor 仅影响构造函数签名和初始化顺序,不改变字段/属性的内存可见性或访问控制 若需并发安全,必须显式使用
Interlocked.Increment(ref counter.Value)
或封装为
private int _value; public int Value => Volatile.Read(ref _value);
不要依赖 Primary Constructor “看起来简洁”就忽略同步原语——它不替代
lock
ConcurrentDictionary
或不可变设计

record 默认不可变,天然适合做并发场景下的数据快照或消息载荷

record Person(string Name, int Age)
声明的类型,所有字段默认是
init
(仅构造时可设),且自动生成值相等性(
Equals
/
GetHashCode
)。这使它成为跨线程传递数据的理想载体:无需担心被意外修改,也不必深拷贝。

典型使用场景:ASP.NET Core 中从

ConcurrentQueue<person></person>
消费数据、Actor 模型中作为消息体、或缓存层返回只读视图。

record 的
with
表达式生成新实例,避免共享可变状态——
var adult = person with { Age = person.Age + 1 };
是线程安全的
注意:如果 record 包含可变引用类型字段(如
public List<string> Tags { get; init; }</string>
),外部仍可修改
Tags.Add(...)
,此时需手动冻结或用
IReadOnlyList<string></string>
struct
不同,record 是引用类型,但因不可变,其引用共享无副作用

混合使用:用 record 封装状态,用普通 class + Primary Constructor 封装行为与并发协调

一个健壮的并发数据模型往往需要“不可变数据 + 可控可变协调器”。例如,用

record OrderEvent(Guid Id, decimal Amount, DateTime Timestamp)
表示事件,再用带 Primary Constructor 的
class OrderProcessor(int maxRetries)
管理重试逻辑和状态机。

这种分层避免了把业务规则和数据契约混在同一类型里,也防止 record 因强行塞入锁或

ConcurrentStack
而失去语义清晰性。

Primary Constructor 适合初始化协调器内部的配置参数(如超时、重试次数、
ConcurrentDictionary
实例),而非业务数据本身
record 字段不应包含
ConcurrentQueue<t></t>
AutoResetEvent
等同步原语——那是协调器的责任
性能影响:record 的值相等性比较比引用比较略慢,但在消息路由、去重等场景中,语义正确性远比微秒级差异重要
public record OrderCreated(Guid OrderId, string Product, decimal Total);
public class OrderDispatcher(TimeSpan dispatchTimeout)
{
    private readonly ConcurrentQueue<OrderCreated> _queue = new();
    private readonly TimeSpan _timeout = dispatchTimeout;
<pre class='brush:php;toolbar:false;'>public void Enqueue(OrderCreated order) => _queue.Enqueue(order);
public bool TryDequeue([NotNullWhen(true)] out OrderCreated? order) =>
    _queue.TryDequeue(out order);

}

真正容易被忽略的是:record 的不可变性只作用于其直接声明的字段。一旦你嵌套了可变对象(比如

public Dictionary<string object> Metadata { get; init; }</string>
),整个“不可变”承诺就失效了——并发下依然可能出错。别指望编译器替你检查深层可变性。

相关推荐