CodeGym /课程 /C# SELF /required 属性和

required 属性和 field 属性

C# SELF
第 17 级 , 课程 3
可用

1. 入门

用C#写代码不用属性也行,但这就像穿着轮滑鞋在白宫里溜达——能用,但很别扭。现在大家都习惯了不用多余 getter/setter 的简洁语法,尤其是当你的应用开始处理“严肃”的模型(比如 User 类,描述系统里的用户)时,保证所有需要的数据都真的有就变得很重要。

比如说,我们有个类:

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

很容易就忘了初始化属性:

User user = new User(); // Name 会是 null, Age = 0

结果就是,等你代码跑了十几个页面、绕了上百个逻辑弯,突然就遇到著名的 NullReferenceException,然后你得花老半天找原因。

回忆一下 init-only 属性

对,你可以只在创建时初始化:

public string Name { get; init; }

但即使这样,没人能强制用你类的人一定要传值——大多数默认构造函数都会用默认值初始化字段(null0 等等)。

那怎么才能让程序员(包括你自己)不忘记给属性赋值呢?这就是 required 修饰符(C# 11 新出的)诞生的原因。

2. required 属性:强制初始化

required 修饰符就是告诉编译器:这个对象的某个属性,创建的时候必须明确赋值。简单说,如果你没初始化这个属性,编译器就不让你过,IDE 还会画个大红波浪线。


public class User
{
    public required string Name { get; set; }
    public int Age { get; set; }
}
required 的必填属性

来试试创建个用户:

// 编译错误: Name 属性必须初始化!
User user1 = new User();

或者这样:


// 编译错误: 没有指定 required 属性 Name
User user2 = new User { Age = 18 };

正确的写法:

User user3 = new User { Name = "赫敏", Age = 18 };

required 怎么工作的?

required 修饰符告诉编译器:“对象构造函数执行完后,这个属性必须被明确赋值。”

这对普通属性和 init-only 属性都适用:

public required string Name { get; init; }

如果你写了自定义构造函数并初始化了 required 属性,也没问题。

常见检查方式

场景 编译器满意吗?
没指定 required 属性
在对象初始化器里指定了 ✔️
在构造函数里指定了 ✔️

在我们的“狗狗”模型里的例子

public class Dog
{
    public required string Name { get; set; }
    public int Age { get; set; }
}

Dog dog = new Dog { Name = "波比", Age = 5 }; // 没问题!
Dog badDog = new Dog { Age = 2 }; // 错误!没指定 Name

required 真的有用的场景?

  • DTO 层间传递: 比如你有 API,必须保证所有必填字段都传进来。
  • 复杂模型有必填属性: 比如 Product 必须有 SKU,Order 必须有订单号。
  • 面试和代码评审: 展示这种写法,别人看你代码会更尊重你(还会有点小羡慕)。

3. required 和构造函数怎么配合?

有时候你会手动写构造函数。如果构造函数或对象初始化器没给 required 属性赋值,编译器就会报错。


public class Article
{
    public required string Title { get; set; }
    public required string Author { get; set; }

    public Article()
    {
        // 如果没初始化 Title 和 Author —— 编译错误!
        // 可以这样:
        Title = "无标题";
        Author = "未知";
    }
}

如果构造函数自己给 required 属性赋值了——没问题。如果没有,你就得用对象初始化器(new Article { ... })来赋值。

使用细节

  • required 只能用在属性,不能用在字段上。
  • required 不会被继承——如果基类属性是 required,子类没写 required,编译器也不会报错(但最好在子类也写上)。
  • required 不能用在自动字段或其他不是 property 的东西上。

4. 箭头属性写法

C# 新版本出来后,大家都追求更简洁、更表达力强的代码。其中一个很酷的新特性就是 箭头属性(也叫 expression-bodied properties)。

有时候你只想定义一个简单返回值的属性,没啥额外逻辑。以前你得写完整的 getter 带大括号:

public int Age
{
    get { return birthYear > 0 ? DateTime.Now.Year - birthYear : 0; }
}

现在可以 更短 地写——用箭头(=>):


public int Age => birthYear > 0 ? DateTime.Now.Year - birthYear : 0;

这种写法叫 表达式体属性(expression-bodied property)。很适合简单计算,让代码更紧凑。

getset 的例子

public class Book
{
    private string _title;

    public string Title
    {
        get => _title;
        set => _title = value.Trim();
    }
}

这里 get 返回字段值,set 赋值前会去掉两边多余空格。

只用 get(只读)的例子:

如果属性只读——可以完全不用大括号,直接用 =>

public class Person
{
    private string name = "马克·吐温";

    // 只读:计算属性
    public string Name => name.ToUpper();
}

这里 Name 属性只能读,总是返回 name 的大写。

5. 属性里的 field 关键字

C# 14 之前,如果你想在 setter 或 getter 里直接访问自动属性的 隐藏字段(比如防止递归或加点自定义逻辑)——做不到。你得自己声明字段。

C# 14 允许你用 field 关键字访问自动属性的隐藏字段:


public class Person
{
    public string Name
    {
        get => field; // field 就是 Name 属性的隐藏字段
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("名字不能为空!");
            field = value; // 用 field 替代 _name
        }
    }
}

以前你得这样写:

private string _name;
public string Name
{
    get => _name;
    set
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("名字不能为空!");
        _name = value;
    }
}

现在少声明一个变量,代码更简洁越好。

为什么有时候要在属性里访问字段?

  • 有时候你想明确控制 值存在哪、怎么存(比如你想返回对象副本、缓存结果或做 lazy-loading)。
  • 如果你要用属性或 reflection——字段名可能会用到。
  • 有些(反)序列化或性能优化场景,你想更细致地控制值的存储。

用法示例:

setter 里做数据校验

public double Grade
{
    get => field;
    set
    {
        if (value < 0 || value > 5)
            throw new ArgumentOutOfRangeException("分数必须在0到5之间");
        field = value;
    }
}

记录数值变化统计

public int StepCount
{
    get => field;
    set
    {
        if (value > field)
        {
            Console.WriteLine($"耶!你多走了 {value - field} 步!");
        }
        field = value;
    }
}

Lazy Load

public string Data
{
    get
    {
        if (field == null)
            field = LoadDataFromDatabase();
        return field;
    }
    set => field = value;
}

6. 常见错误和注意点

错误1:忘了初始化 required 属性。
编译器不会让你过,直接报错,帮你避免运行时出问题。

错误2:构造函数没完全初始化 required 属性。
如果构造函数没给所有必填属性赋值,编译器会提醒你漏了点啥。

错误3:试图把 required 和 constreadonly 一起用。
这些修饰符不兼容——required 只能用在普通属性上。混用会报错。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION