class/struct 主构造函数——参数捕获机制、DI 模式、继承规则
public record Person(string Name, int Age);
// Name, Age → public init-only 属性 + Deconstruct + Equals…
public class Person(string name, int age)
{
public string Name => name; // 手动暴露
public int Age => age;
}
// name, age → 不是属性,外部不可见
编译器做逃逸分析——参数出现在哪里决定是否分配字段:
| 参数被引用的位置 | 示例 | 分配字段? |
|---|---|---|
仅 base(...) / this(...) | : base(name) | ❌ 否 |
| 仅属性/字段初始化器 | public string X { get; } = x; | ❌ 否 |
| 表达式体属性 getter | public int X => x; | ✅ 是 |
| 方法体、实例成员 | ToString() => name; | ✅ 是 |
| 未被任何成员引用 | — | ❌ 否 + 警告 |
public string X => x;(表达式体,捕获) vs public string X { get; } = x;(初始化器,不捕获)。前者每次访问都读参数,后者只在构造时读一次存入属性的 BackingField。
捕获的字段 IL 名为 <param>P,不可在 C# 中直接引用。字段是 private 的——即使在继承层次中派生类也访问不到。
// C# 11
public class OrderService : BaseService
{
private readonly ILogger _logger;
private readonly IOrderRepo _repo;
public OrderService(ILogger l, IOrderRepo r) { _logger = l; _repo = r; }
}
// C# 12
public class OrderService(ILogger _logger, IOrderRepo _repo) : BaseService
{
// _logger, _repo 直接使用——下划线前缀参数名即字段名
}
| 场景 | 规则 |
|---|---|
| 同类的显式构造 | 必须 : this(...) 调用主构造 |
| 派生类 | 必须 : base(...) 把参数传给基类 |
| class + 主构造 | 不生成隐式无参构造 |
| struct + 主构造 | 始终有无参构造(CLR 强制) |
// 派生类示例——每个类独立捕获
public class Animal(string species) // Animal 捕获 species
{
public string Species => species;
}
public class Bird(string species, string name) : Animal(species)
{
// species 出现在 base(species) 中 → 传给基类
// species 又出现在 SpeciesName 中 → Bird 自己也要捕获!
public string SpeciesName => species;
public string Name => name;
}
// 两个独立的 <species>P 字段——Animal 一个,Bird 一个
name + 派生类 name),编译器不报错,但读者要分辨哪个是哪个。建议用不同名字区分。
主构造参数不是 readonly——C# 12/13 都不支持 readonly 修饰。
public class Service(ILogger _logger)
{
void Bad() { _logger = null; } // 编译通过!
}
// 传统 DI:readonly 字段 → 编译器保证不可变
// 主构造:全靠纪律——你要 readonly 保证?退回传统写法。
// [Obsolete] 放 class 上 → 标记类本身
[Obsolete("Use V2")]
public class OldService(IDb db) { }
// method: 目标 → 标记构造函数本身
[method: Obsolete("Use V2 ctor")]
public class OldService(IDb db) { }
readonly 保证| 你的场景 | 用 Primary Constructor? |
|---|---|
| DI:Service / Controller 注入 2-3 个依赖,透传给基类 | ✅ 最佳 |
| DTO / Model:需要外部可读的属性 | ❌ 用 record |
| 构造函数有校验逻辑 | ❌ 传统构造 |
| struct 数据载体,只需少量暴露 | ✅ 可用 |