【.NET新特性·第11篇】C# 14 字段属性:告别手写 backing field

field 关键字让你在属性访问器中直接使用编译器合成的 backing field,告别手写样板代码

版本定位

适用版本:.NET 10 | C# 14 前置知识:C# 13 基础、属性和 backing field 概念

背景

在 C# 中,如果你需要在属性的 get/set 访问器中添加逻辑,通常需要手写 backing field:

复制代码
// C# 13 及之前
public class Person
{
    private string _name;

    public string Name
    {
        get => _name;
        set => _name = value?.Trim() ?? string.Empty;
    }
}

这种写法有两个问题:

  1. 需要手写 backing field(_name)

  2. 需要手写赋值逻辑(= value)

C# 14 引入了 field 关键字,让编译器自动合成 backing field。

field 关键字详解

基本用法

复制代码
// C# 14
public class Person
{
    public string Name
    {
        get => field;
        set => field = value?.Trim() ?? string.Empty;
    }
}

field 是什么?

  • 编译器自动合成的 backing field

  • 在 get 访问器中,field 是当前值

  • 在 set 访问器中,field 是要设置的新值

只在 get 中使用

复制代码
public class Product
{
    public decimal Price { get; set; }

    public decimal PriceWithTax
    {
        get => field == 0 ? Price * 1.1m : field;
        set => field = value;
    }
}

只在 set 中使用

复制代码
public class User
{
    public string Email
    {
        get => field;
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentException("Email cannot be empty");
            field = value.ToLower();
        }
    }
}

与验证逻辑配合

复制代码
public class Age
{
    public int Value
    {
        get => field;
        set
        {
            if (value < 0 || value > 150)
                throw new ArgumentOutOfRangeException(nameof(value));
            field = value;
        }
    }
}

与旧写法对比

场景 1:简单的值转换

复制代码
// C# 13 及之前
private string _name;
public string Name
{
    get => _name;
    set => _name = value?.Trim();
}

// C# 14
public string Name
{
    get => field;
    set => field = value?.Trim();
}

场景 2:验证 + 赋值

复制代码
// C# 13 及之前
private int _age;
public int Age
{
    get => _age;
    set
    {
        if (value < 0)
            throw new ArgumentOutOfRangeException(nameof(value));
        _age = value;
    }
}

// C# 14
public int Age
{
    get => field;
    set
    {
        if (value < 0)
            throw new ArgumentOutOfRangeException(nameof(value));
        field = value;
    }
}

场景 3:计算属性

复制代码
// C# 13 及之前
private DateTime _createdTime;
public DateTime CreatedTime
{
    get => _createdTime;
    set => _createdTime = value.ToUniversalTime();
}

public DateTime CreatedTimeLocal => _createdTime.ToLocalTime();

// C# 14
public DateTime CreatedTime
{
    get => field;
    set => field = value.ToUniversalTime();
}

public DateTime CreatedTimeLocal => CreatedTime.ToLocalTime();

注意事项

1. 字段名冲突

如果类中已经有一个名为 field 的成员,会发生冲突:

复制代码
public class Example
{
    private int field;  // 字段

    public int Value
    {
        get => field;  // 这里是字段,不是 keyword
        set => field = value;
    }
}

解决方案 :使用 @field 或 this.field 来消歧义

复制代码
public class Example
{
    private int field;

    public int Value
    {
        get => @field;  // 使用 @ 消歧义
        set => @field = value;
    }
}

2. 不能用于自动属性

复制代码
// 编译错误
public int Value { get => field; set => field = value; }

// 正确:自动属性
public int Value { get; set; }

// 正确:使用 field 的属性
public int Value
{
    get => field;
    set => field = value;
}

3. 不能用于表达式-bodied 属性

复制代码
// 编译错误
public int Value => field;

// 正确
public int Value
{
    get => field;
}

实战场景

DTO 类

复制代码
public class UserDto
{
    public string Name
    {
        get => field;
        set => field = value?.Trim() ?? string.Empty;
    }

    public string Email
    {
        get => field;
        set => field = value?.ToLower().Trim();
    }

    public int Age
    {
        get => field;
        set
        {
            if (value < 0 || value > 150)
                throw new ArgumentOutOfRangeException(nameof(value));
            field = value;
        }
    }
}

配置类

复制代码
public class AppConfig
{
    public string ConnectionString
    {
        get => field;
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentException("Connection string cannot be empty");
            field = value;
        }
    }

    public int MaxRetries
    {
        get => field;
        set => field = Math.Max(0, Math.Min(10, value));
    }

    public TimeSpan Timeout
    {
        get => field;
        set => field = value > TimeSpan.Zero ? value : TimeSpan.FromSeconds(30);
    }
}

迁移建议

自动迁移

field 关键字是向后兼容的,可以直接使用:

复制代码
<PropertyGroup>
    <LangVersion>14</LangVersion>
</PropertyGroup>

逐步迁移

复制代码
// 旧代码
private string _name;
public string Name
{
    get => _name;
    set => _name = value?.Trim();
}

// 新代码
public string Name
{
    get => field;
    set => field = value?.Trim();
}

// 删除 _name 字段

一句话总结

field 关键字让你告别手写 backing field,用更简洁的语法实现属性的 get/set 逻辑。


官方文档


📦 示例代码:.NET 新特性巡礼全系列配套示例代码(含 dotnet 8/9/10)

💬 欢迎点赞、收藏、转发,你的支持是我持续创作的动力!

相关推荐
代码什么用5 小时前
Spring基础使用
java·后端·spring
Su米苏5 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
码云数智-园园5 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁6 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网6 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端
禾小西6 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨6 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言
小卡车5557 小时前
java反射、自定义注解的应用(自定义分页)
java
BLUcoding7 小时前
接口服务公网超时排查记录:SecureRandom 阻塞问题分析与解决
java·linux·springboot·aes·securerandom
yuniko-n8 小时前
【JUC】Lock 和 synchronzied 锁
java