C# 属性(Property)(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言

在 C# 开发中,属性(Property) 是面向对象编程的核心概念之一。它不仅是数据封装的桥梁,更是代码规范性与可维护性的关键。对于编程初学者而言,理解属性的定义、作用及使用场景,能够帮助快速掌握面向对象的设计思想;对于中级开发者,深入探索属性的高级特性(如延迟加载、索引器等),则能显著提升代码的优雅度与复用性。本文将从基础到进阶,结合实例与比喻,系统性地讲解 C# 属性的方方面面。


一、属性的基础概念与核心作用

1.1 什么是属性?

属性(Property) 是一种用于访问对象状态的特殊成员,它通过 getset 访问器控制对字段(Field)的读写操作。简单来说,属性是“包装”字段的接口,对外隐藏了字段的具体实现细节。

比喻
可以将属性想象为银行保险箱的“访问接口”。客户(调用者)通过属性(如 GetBalance()SetBalance())操作账户金额,而无需直接接触保险箱(字段)的物理结构。

public class BankAccount
{
    private decimal _balance; // 字段(隐藏的)

    public decimal Balance // 属性
    {
        get { return _balance; }
        set { _balance = value; }
    }
}

1.2 属性的核心作用

  1. 数据封装:通过属性访问字段,避免外部直接修改字段值,确保数据的一致性。
  2. 逻辑控制:在 getset 中添加验证、计算或日志记录等逻辑。
  3. 接口统一:即使字段的存储方式(如从数据库或内存中获取)发生变化,属性的外部接口保持不变。

二、属性的访问器(Accessor)详解

2.1 getset 访问器

每个属性可以包含 getset 两个访问器,分别用于获取和设置属性值。

示例:基础属性

public class Student
{
    private string _name; // 私有字段

    public string Name // 公共属性
    {
        get { return _name; } // 获取值
        set { _name = value; } // 设置值,value 是隐含的临时变量
    }
}

简写形式(自动属性)

C# 允许通过自动属性简化代码:

public string Name { get; set; } // 等价于上述代码

2.2 只读属性与只写属性

  • 只读属性:仅包含 get 访问器,常用于返回计算值或不可修改的数据。
  • 只写属性:仅包含 set 访问器,但实际开发中较少使用。

示例:只读属性

public class Rectangle
{
    private int _width;
    private int _height;

    public int Area // 只读属性
    {
        get { return _width * _height; }
    }
}

比喻
只读属性就像超市的“只读价目表”,顾客可以看到价格,但无法直接修改。


三、属性的高级特性与应用场景

3.1 延迟加载(Lazy Loading)

通过 get 访问器实现延迟初始化,仅在首次访问时加载数据,提升性能。

示例:延迟加载图片资源

public class ImageLoader
{
    private Bitmap _image;
    private readonly string _filePath;

    public ImageLoader(string path)
    {
        _filePath = path;
    }

    public Bitmap Image // 属性实现延迟加载
    {
        get
        {
            if (_image == null)
                _image = LoadImageFromDisk(_filePath);
            return _image;
        }
    }

    private Bitmap LoadImageFromDisk(string path)
    {
        // 模拟耗时的磁盘读取操作
        return new Bitmap(path);
    }
}

3.2 索引器(Indexer)

索引器允许像数组一样通过索引访问对象的成员,本质是一种特殊类型的属性。

示例:自定义集合类的索引器

public class MyCollection<T>
{
    private List<T> _items = new List<T>();

    public T this[int index] // 索引器属性
    {
        get { return _items[index]; }
        set { _items[index] = value; }
    }
}

3.3 表达式主体属性

C# 6.0 引入了表达式主体属性(Expression-Bodied Properties),进一步简化代码。

public class Circle
{
    private double _radius;

    public double Area => Math.PI * _radius * _radius; // 等价于只读属性
}

四、最佳实践与注意事项

4.1 属性的命名规范

  • 遵循 PascalCase 命名规则(如 CustomerName)。
  • 避免与字段名称重复(如字段 _balance 对应属性 Balance)。

4.2 避免副作用

set 访问器中应仅执行与属性值相关的操作,避免引发复杂逻辑(如网络请求或数据库操作)。

错误示例:

public class InvalidExample
{
    public int Count
    {
        set 
        {
            // 不好的做法:设置 Count 同时触发网络请求
            _count = value;
            SendCountToServer(); 
        }
    }
}

4.3 使用特性(Attribute)增强属性

通过自定义特性(如 [Required])为属性添加元数据,常用于数据验证或序列化场景。

public class User
{
    [Required(ErrorMessage = "姓名不能为空")]
    public string Name { get; set; }
}

五、实战案例:综合应用属性

案例 1:学生信息管理系统

public class Student
{
    public string Name { get; set; }

    private int _age;
    public int Age 
    {
        get => _age;
        set 
        {
            if (value < 0 || value > 150)
                throw new ArgumentOutOfRangeException("年龄应在 0-150 之间");
            _age = value;
        }
    }
}

案例 2:银行账户的余额验证

public class BankAccount
{
    private decimal _balance;

    public decimal Balance 
    {
        get => _balance;
        set 
        {
            if (value < 0)
                throw new InvalidOperationException("余额不能为负数");
            _balance = value;
        }
    }
}

六、属性在框架与设计模式中的应用

6.1 在 ASP.NET Core 中的 Model Binding

控制器的参数通过属性名与 HTTP 请求的键自动绑定,依赖属性的命名一致性。

6.2 在 MVVM 模式中的数据绑定

WPF 或 UWP 中,ViewModel 的属性通过 INotifyPropertyChanged 接口实现动态更新。

public class PersonViewModel : INotifyPropertyChanged
{
    private string _name;
    public string Name 
    {
        get => _name;
        set 
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

结论

C# 属性(Property) 是构建健壮、可维护代码的核心工具。通过封装字段、控制访问逻辑、实现延迟加载或索引器等高级特性,开发者能够显著提升代码的规范性与扩展性。无论是基础的 get/set 访问器,还是结合设计模式的复杂场景,属性始终扮演着“数据守护者”的角色。建议读者在实践中多尝试通过属性实现业务逻辑的解耦,并结合特性、索引器等进阶功能,逐步掌握 C# 属性的精髓。

最新发布