C# required 关键字详解

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 提示 ✅ 智能提示 ❌ 需运行才知
性能影响 有反射开销
默认值支持
适用场景 强制初始化 依赖注入 模型验证

七、最佳实践建议

  1. 对必须的业务属性使用:如 ID、名称等核心字段

  2. 替代过多构造器:当参数超过 3-4 个时,考虑使用 required + 对象初始化器

  3. 配合 init 使用 :创建不可变对象

    csharp 复制代码
    public required string Id { get; init; }
  4. API 契约定义:明确告诉调用方哪些字段必须提供

  5. 避免滥用:简单对象或参数很少时,传统构造器更直观

required 本质上是编译时的契约约束,让代码更安全、更自文档化,特别适合现代 C# 的对象初始化器风格。

相关推荐
日取其半万世不竭7 小时前
用 Netdata 实时监控服务器,比 Prometheus + Grafana 轻量得多
linux·服务器·网络·系统架构·负载均衡·zabbix·grafana
曹牧7 小时前
Java:处理 HTTP 请求的 Content-Type
java·开发语言
itzixiao8 小时前
L1-066 猫是液体(5分)[java][python]
java·开发语言·python·算法
Lightning-py8 小时前
Python 配置日志(Logging)
开发语言·python
2401_881828328 小时前
交换综合实验报告
网络
d111111111d8 小时前
了解Modbus
网络·笔记·stm32·单片机·嵌入式硬件·学习
隔窗听雨眠8 小时前
MySQL主从延迟根因诊断法
开发语言·php
Hui_AI7208 小时前
基于RAG的农产品GEO溯源智能问答系统实现
开发语言·网络·人工智能·python·算法·创业创新
CDwenhuohuo8 小时前
前端文件预览
开发语言·前端·javascript
charlie1145141918 小时前
通用GUI编程技术——图形渲染实战(三十八)——顶点缓冲与输入布局:GPU的第一个三角形
开发语言·c++·学习·图形渲染·win32