get与set方法
在面向对象编程(OOP)中,是不允许外界直接对类的成员变量直接访问的,既然不能访问,那定义这些成员变量还有什么意义呢?所以C#中就要用set和get方法来访问私有成员变量,它们相当于外界访问对象的一个通道,一个“接口”。先来看一段代码:
class Employee
{
private string name;
private byte age;
public string Name
{
get { return name; }
set { name = value; }
}
public byte Age
{
get { return age; }
set { age = value; }
}
}
代码中定义了两个私有变量name和age,当我们不想让外界随意访问该私有变量时,可以使用属性来访问,语法为:
public <返回类型(要与被访问变量的类型相同)> <属性名(不能与被访问变量同名)>
{
get{ return <被访问变量>;}
set{ <被访问变量> = value;}
}
当我们使用属性来访问私有成员变量时就会调用里面的get方法,当我们要修改该变量时就会调用set方法,当然在定义的时候可以只定义一个get方法或只定义一个set方法。如果只定义get方法,那么这个相应变量就是“只读”的;如果只定义set方法,那么相应变量就是“只写”的。
既然外界可以通过set和get访问类内私有成员,那为什么不直接把它定义成共有,直接供外界访问呢?拿上面的Employee类来说明:
class Employee
{
private string name;
private byte age;
public string Name
{
get { return name; }
set { name = value; }
}
//****修改后****↓↓↓↓↓↓↓↓
public byte Age
{
get { return age; }
set
{
if (value > 10 && value<=100) //一般在公司雇员的年龄都在10到100岁之间
age = value;
}
}
//****修改后****↑↑↑↑↑↑↑↑
}
上例中,set就像一位门卫大叔一样,只有好人才能进来。可以通过属性来控制对员变量的读写,防止对成员变量的非法赋值等。
再举个小例子,以下是个温度计的类:
class Thermometer
{
private double temperature;
public Thermometer(double temperature) //构造函数
{
this.temperature = temperature;
}
public double Temperature
{
get { return temperature; }
set { temperature = value; }
}
}
假设这里的温度单位表示的是摄氏度(℃),如果在需求分析阶段误解了客户的原始意愿或者客户在日后变更了需求,需要把系统中所有表示温度的变量temperature都用来表示开尔文温度(K)。那么不必动系统中成百上千个Temperature属性,只需在get和set函数中稍微修改一下代码即可:
class Thermometer
{
private double temperature;
public Thermometer(double temperature) //构造函数
{
this.temperature = temperature;
}
public double Temperature
{
//****修改后****↓↓↓↓↓↓↓↓
get { return temperature-273.15; }
set { temperature = value+273.15; }
//****修改后****↑↑↑↑↑↑↑↑
}
}
建一个控制台应用程序来测试一下,主函数为:
class Test
{
static void Main(string[] args)
{
Thermometer a = new Thermometer(40);
Console.WriteLine(a.Temperature);
}
}
上面代码中用构造函数给温度设的初值为40度,代码修改前运行结果为“40”,代码修改后运行结果为“-233.15”。
属性(Property)
属性(property)是一种用于访问对象或类的特性的成员。它提供灵活的机制来读取、编写或计算私有字段的值。
无参属性
一般看来,常量和字段已经足够刻画实际生活中对象的各种参数。实际上也确实是这样。属性(property)是一种"高级字段”,它可能带有一个 getter 和一个 setter,它们保护属性的值,使之不会被外部胡乱篡改。和字段相比,属性实现了对成员的封装。
在 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.getC
和 A.setC
方法了。
实际上,无参属性仅仅是语法糖。通过编译之后使用 iladsm 查看,我们可以发现,编译器自动为我们生成了 get_c 和 sct_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 机,我们通过它暴露的有限功能来实现存钱取钱,查询余额等操作,但对于它是如何实现的(例如,钱到底放在了哪儿)一无所知。
通过封装,类型只需要向外部提供它应该知道的信息,否则就会出现“你知道的太多了”这种情形。
参考链接
https://blog.csdn.net/u013095889/article/details/52804360
http://c.biancheng.net/view/3087.html