C# required 关键字详解
required 是 C# 11(.NET 7)引入的特性,用于强制要求在创建对象时必须初始化某个属性或字段。
一、基本用法
csharp
public class Person
{
public required string Name { get; set; }
public required int Age { get; set; }
public string? Email { get; set; } // 可选
}
// 正确的使用方式 - 必须初始化 required 成员
var person1 = new Person
{
Name = "张三",
Age = 25
};
// 错误 - 缺少 required 成员
var person2 = new Person
{
Name = "李四"
// 编译错误:未初始化 required 属性 'Age'
};
// 错误 - 使用构造器也不行(除非构造器也标记了 SetsRequiredMembers)
var person3 = new Person("王五", 30); // 编译错误
二、进阶用法
1. 与构造器配合使用
csharp
public class Product
{
public required int Id { get; init; }
public required string Name { get; set; }
public decimal Price { get; set; }
// 标记构造器会初始化所有 required 成员
[SetsRequiredMembers]
public Product(int id, string name)
{
Id = id;
Name = name;
}
// 普通构造器不会解除 required 约束
public Product() { }
}
// 使用 SetsRequiredMembers 构造器 - 合法
var p1 = new Product(1, "电脑");
// 使用普通构造器 - 仍需对象初始化器
var p2 = new Product { Id = 2, Name = "手机" };
2. 与 record 类型配合
csharp
public record class User
{
public required string UserName { get; init; }
public required string Password { get; init; }
public string? NickName { get; init; }
}
// 使用对象初始化器
var user = new User
{
UserName = "admin",
Password = "123456"
};
3. 字段也支持 required
csharp
public class Config
{
public required string ConnectionString;
public required int Timeout;
}
var config = new Config
{
ConnectionString = "Server=localhost",
Timeout = 30
};
三、框架支持
| 框架/版本 | 支持情况 | 最低版本 |
|---|---|---|
| .NET | ✅ 支持 | .NET 7+ |
| .NET Core | ❌ 不支持 | - |
| .NET Standard | ❌ 不支持 | - |
| ASP.NET Core | ✅ 支持 | 7.0+ |
| Entity Framework Core | ✅ 支持 | 7.0+ |
| Newtonsoft.Json | ⚠️ 有限支持 | 需要配置 |
| System.Text.Json | ✅ 支持 | 7.0+ |
| 反射/Emitting | ✅ 支持 | 需手动处理 |
各框架具体支持情况:
csharp
// ASP.NET Core 7+ - 自动支持模型验证
public class LoginModel
{
public required string Username { get; set; }
public required string Password { get; set; }
}
// EF Core 7+ - 支持 required 属性作为必须字段
public class Student
{
public int Id { get; set; }
public required string Name { get; set; } // 数据库 NOT NULL
public required int Age { get; set; }
}
// 生成的迁移会创建 NOT NULL 列
四、常见使用场景
场景1:DTO/ViewModel 强制必填字段
csharp
// API 请求模型
public class CreateOrderRequest
{
public required int UserId { get; set; }
public required decimal Amount { get; set; }
public required List<OrderItem> Items { get; set; }
public string? Remark { get; set; } // 可选
}
// 确保调用方不会遗漏必填字段
场景2:配置类
csharp
public class DatabaseConfig
{
public required string Host { get; init; }
public required int Port { get; init; }
public required string DatabaseName { get; init; }
public string? Username { get; init; }
public string? Password { get; init; }
}
// 使用
var config = new DatabaseConfig
{
Host = "localhost",
Port = 5432,
DatabaseName = "MyDb"
};
场景3:领域对象的核心属性
csharp
public class Order
{
public required string OrderNo { get; init; }
public required DateTime CreateTime { get; init; }
public required decimal TotalAmount { get; set; }
public string? CustomerNote { get; set; }
}
场景4:避免构造器重载爆炸
传统方式(构造器过多):
csharp
public class EmailService
{
public string SmtpServer { get; }
public int Port { get; }
public string Username { get; }
public EmailService(string smtpServer) : this(smtpServer, 25) { }
public EmailService(string smtpServer, int port) : this(smtpServer, port, null) { }
public EmailService(string smtpServer, int port, string? username) { }
// 构造器组合爆炸...
}
使用 required(更清晰):
csharp
public class EmailService
{
public required string SmtpServer { get; init; }
public required int Port { get; init; } = 25; // 默认值
public string? Username { get; init; }
}
var service = new EmailService
{
SmtpServer = "smtp.gmail.com",
Port = 587,
Username = "user@gmail.com"
};
五、注意事项与限制
1. 不能与某些特性同时使用
csharp
public class Test
{
// ❌ 不能与 init 同时使用?实际上可以,但初始化后不可变
public required string Name { get; init; } // ✅ 允许
// ❌ required 属性不能有 set 访问器?可以,但需理解含义
public required int Age { get; set; } // ✅ 允许,但初始化后仍可修改
}
2. 继承时的 required 约束
csharp
public class Base
{
public required int Id { get; set; }
}
public class Derived : Base
{
public required string Name { get; set; }
}
// 必须初始化所有 required(包括父类)
var obj = new Derived
{
Id = 1,
Name = "Test"
};
3. 反序列化支持
csharp
// System.Text.Json 7+ 自动支持
var json = @"{""Name"":""张三"",""Age"":25}";
var person = JsonSerializer.Deserialize<Person>(json); // ✅ 正常工作
// Newtonsoft.Json 需要配置
[JsonObject(Required = Required.Always)]
public class Person
{
public required string Name { get; set; }
}
六、与可选参数的对比
| 特性 | required |
构造器参数 | [Required] 特性 |
|---|---|---|---|
| 检查时机 | 编译时 | 编译时 | 运行时 |
| IDE 提示 | ✅ 智能提示 | ✅ | ❌ 需运行才知 |
| 性能影响 | 无 | 无 | 有反射开销 |
| 默认值支持 | ✅ | ✅ | ❌ |
| 适用场景 | 强制初始化 | 依赖注入 | 模型验证 |
七、最佳实践建议
-
对必须的业务属性使用:如 ID、名称等核心字段
-
替代过多构造器:当参数超过 3-4 个时,考虑使用 required + 对象初始化器
-
配合 init 使用 :创建不可变对象
csharppublic required string Id { get; init; } -
API 契约定义:明确告诉调用方哪些字段必须提供
-
避免滥用:简单对象或参数很少时,传统构造器更直观
required 本质上是编译时的契约约束,让代码更安全、更自文档化,特别适合现代 C# 的对象初始化器风格。