贝利信息

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

日期:2026-01-23 00:00 / 作者:星降
Primary Constructors 不提供线程安全,仅简化构造语法;record 天然不可变,适合并发数据传递;二者应分层使用:record 封装不可变数据,带 Primary Constructor 的 class 封装可变协调逻辑。

Prim

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

Primary Constructors 是 C# 12 引入的语法糖,它让构造逻辑更紧凑,但本身不提供线程安全保证。如果你写 public class Counter(int initial = 0) { public int Value = initial; }Value 是公开字段,多个线程同时读写会引发竞态——编译器不会自动加锁或转为 volatile

常见错误现象:在高并发计数场景中,counter.Value++ 看似原子,实则被拆成“读-改-写”三步,结果远小于预期值。

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

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

典型使用场景:ASP.NET Core 中从 ConcurrentQueue 消费数据、Actor 模型中作为消息体、或缓存层返回只读视图。

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

一个健壮的并发数据模型往往需要“不可变数据 + 可控可变协调器”。例如,用 record OrderEvent(Guid Id, decimal Amount, DateTime Timestamp) 表示事件,再用带 Primary Constructor 的 class OrderProcessor(int maxRetries) 管理重试逻辑和状态机。

这种分层避免了把业务规则和数据契约混在同一类型里,也防止 record 因强行塞入锁或 ConcurrentStack 而失去语义清晰性。

public record OrderCreated(Guid OrderId, string Product, decimal Total);
public class OrderDispatcher(TimeSpan dispatchTimeout)
{
    private readonly ConcurrentQueue _queue = new();
    private readonly TimeSpan _timeout = dispatchTimeout;
public void Enqueue(OrderCreated order) => _queue.Enqueue(order);

public bool TryDequeue([NotNullWhen(true)] out OrderCreated? order) =>
    _queue.TryDequeue(out order);

}

真正容易被忽略的是:record 的不可变性只作用于其直接声明的字段。一旦你嵌套了可变对象(比如 public Dictionary Metadata { get; init; }),整个“不可变”承诺就失效了——并发下依然可能出错。别指望编译器替你检查深层可变性。