首页 > 编程笔记 > C#笔记

C#属性(Property)

属性(property)是一种用于访问对象或类的特性的成员。

属性提供灵活的机制来读取、编写或计算私有字段的值。

属性提供了一种机制,它把读取和写入对象的某些特性与一些操作关联起来。

可以像使用公共数据成员一样使用属性,但实际上属性是称为“访问器”的一种特殊方法,这使得数据在被轻松访问的同时,仍能提供方法的安全性和灵活性。

无参属性

一般看来,常量和字段已经足够刻画实际生活中对象的各种参数。实际上也确实是这样。

属性(property)是一种"高级字段”,它可能带有一个 getter 和一个 setter,它们保护属性的值,使之不会被外部胡乱篡改。

和字段相比,属性实现了对成员的封装。

Java 中有相似的概念(JavaBeans),它要求类中的成员都是私有的,并且提供一个 public 的 getter 和一个 setter,用于让外界访问和修改私有字段的值。

这两个方法的名称以 get 和 set 开头,需要自己手写。

C# 中,也可以通过字段和一对读写方法,自己手动实现属性:
class A
{
    private int c;
    public int getC()
    {
        return c;
    }
    public void setC(int value)
    {
        c = value;
    }
}
在这里的私有字段称为支持字段(Backing Field)。

不过,这样做有两个明显缺点,一是必须手打这些代码;二是在访问属性时,必须调用方法,而不能直接使用点号加属性名。

CLR 提供了称为属性的机制,解决了这两个缺点。下面的写法是经过简化了的写法:

private int c { get;  set;  }

如果不想属性有任何特殊行为,从 C# 3 开始,可以使用简易语法get; set;

这样创建的属性叫做自动实现的属性。另外,我们可以直接通过 A.c 访问属性,而非使用 A.getCA.setC 方法了。

实际上,无参属性仅仅是语法糖。通过编译之后使用 iladsm 查看,我们可以发现,编译器自动为我们生成了 get_c 和 sct_c 方法,以及一个支持字段 <c>k_BackingField,如下图所示。

自动实现的属性

1) 只读和只写属性

可以通过将 get 或 set 设置为 private 获得只读和只写属性。

例如,如果 set 是私有的,则属性就是只读的。不过,属性的值仍然可以被类型内部的成员修改:

public int c { get; private set; }

从 C# 6 开始,允许省略 set 获得真正具有不变性的属性:

public int c { get; }

此时这个字段就真正地具有了不变性,当你初始化了这个字段的值之后,就再也无法更改它的值。

2) 带有逻辑的属性

通过为属性的 get 和 set 中加入代码,我们可以控制属性的取值范围。例如,要实现一个永远为非负整数的属性:
private int age;
public int Age
{
    get
    {
        return age;
    }
    set
    {
        if (value < 0)
        {
            throw new ArgumentOutOfRangeException("Age", value, "Age必须大于等于0");
        }
        age = value;
    }
}
在这段代码中,关键字 value 代表赋值时传入的新值。

有参属性

无参属性的 get 方法不支持参数,而有参属性的 get 方法支持传入一个或更多参数,set 方法支持传入两个或更多参数。

C# 中的有参属性又叫索引器,顾名思义,它是重载[]操作符的一种方式。

虽然有参属性(索引器)很少被使用,不过,它有一个常用场景是这样的:一个类的成员包括一个集合。

比如,记录每天 24 小时温度的 DayTemperature 类,具有一个长度为 24 的 double 集合成员 Temperatures。

当拿到这个类的一个实例 d 时,访问它的成员需要 d.Temperatures[6](代表早上 6 点的温度)。

如果我们对这个类实现有参属性,可以直接使用 d[6] 获得相同的值,省去了集合成员名称 Temperatures。

这种表示法不仅简化了客户端应用程序的语法,还使其他开发人员能够更加直观地理解类及其用途。

索引器至少要定义一个访问方法(即 get 或 set)。
class Program
{
    static void Main(string[] args)
    {
        var d = new DayTemperature();
        d.temperature[0] = 20.5;
        d.temperature[1] = 22;
        //使用类索引器访问
        Console.WriteLine(d[1]);        //22
        Console.ReadKey();
    }
}
class DayTemperature
{
    public double[] temperature = new double[24];
    //类的索引器
    public double this[int index]
    {
        get
        {
            //检查索引范围
            if (index < 0 || index >= temperature.Length)
            {
                return -1;
            }
            else
            {
                return temperature[index];
            }
        }
        set
        {
            if (!(index < 0 || index >= temperature.Length))
            {
                temperature[index] = value;
            }
        }
    }
}

属性的意义

通过属性的封装,我们保留了它与外部交互的能力,又实现了一种可靠的读写机制。

私有的字段、属性和方法保护了这些成员,使它们不会被外界调用,因为这是外界无需知道的信息。

生活中一个典型的封装例子就是 ATM 机,我们通过它暴露的有限功能来实现存钱取钱,查询余额等操作,但对于它是如何实现的(例如,钱到底放在了哪儿)一无所知。

通过封装,类型只需要向外部提供它应该知道的信息,否则就会出现“你知道的太多了”这种情形。

所有教程

优秀文章