属性既维持了封装性,又可以使用字段风格的语法。
什么是属性
属性是字段和方法的交集——看起来像字段,用起来像方法。访问属性所使用的语法和访问字段相同。然而,编译器会将这种风格的语法自动转换成对特定访问器方法的调用。属性的声明如下所示:
访问修饰符 类型 属性名
{
get
{
//取值代码
}
set
{
//赋值代码
}
}
属性可以包含两个代码块,分别以get和set关键字开头。其中,get块包含读取属性时执行的语句,set块包含向属性写入时执行的语句。属性的类型指定了由get和set访问器读取和写入的数据的类型。
所有set访问器都用一个隐藏的、内建的参数(名为value)来传递要写入的数据。
struct ScreenPosition{
private int _x,_y;
public ScreenPosition(int X,int Y)
{
this._x = rangeCheckedX(X);
this._y = rangeCheckedY(Y);
}
public int X //定义属性
{
get {return this._x;}
set{this._x = rangeCheckedX(value);}
}
public int Y //定义属性
{
get {return this._y;}
set{this._y = rangeCheckedY(value);}
}
private static int rangeCheckedX(int x){.......}
private static int rangeCheckedY(int y){.......}
}
属性唯一要求的就是由get访问器返回指定类型的值。值还能动态计算获得,不一定要从存储好的数据中获取。
使用属性
在表达式中使用属性时,要么取值,要么赋值。
ScreenPosition origin = new ScreenPosition(0,0);
//取值,调用后get访问器
int xpos = origin.X //实际调用origin.X.get
int ypos = origin.Y //实际调用origin.Y.get
//赋值,调用set访问器
origin.X = 40; //实际
origin.X += 40; //同时取值和赋值,get和se调用origin.X.set , value = 40
origin.Y = 100; //实际调用origin.Y.set , value = 100t访问器都会被调用
只读属性:可以声明只包含get访问器的属性,这称为只读属性。只读属性不包含set访问器,对其进行写入操作会报告编译错误。
只写属性:类似的,可声明只包含set访问器的属性,这称为只写属性。只写属性不包含get访问器,对其进行读取操作会报告编译错误。只写属性适用于对密码这样的数据进行保护。
属性在外观、行为和感觉上都像字段。但属性本质上是方法而不是字段。另外,属性存在以下限制:
1、只有在结构或类初始化好之后,才能通过该结构或类的属性来赋值。
ScreenPosition location;
location.X = 40;//编译错误,location尚未赋值
2、不能将属性作为ref和out参数传给方法;但可写的字段能作为ref和out参数值传递。
MyMethod(ref location.X); //编译时错误
3、属性中最多只能包含一个get和一个set访问器。不能包含其他方法、字段或属性。
4、get和set访问器不能获取任何参数。要赋的值会通过内建的、隐藏的value变量自动传给set访问器。
5、不能声明const属性。
const int X{ get{...} set{...} }//编译错误
在接口中声明属性
除了可以在接口中定义方法之外,还可以定义属性。为此,需要指定get或set关键字,或者同时指定这两个关键字。但要将get或set访问器的主体替换成分号。如下:
interface IScreenPosition
{
int X {get;set;} //没有修饰符
int Y{get;set;} //没有修饰符
}
实现这个接口的任何类或结构都必须实现X和Y属性,并在属性中定义get和set访问器。如下:
struct ScreenPosition: IScreenPosition
{
.............
public int X
{
get{....}
set{.....}
}
public int Y
{
get{....}
set{.....}
}
..............
}
在类中实现接口的属性时,可将属性的实现声明为virtual,从而允许派生类重写实现。如下:
struct ScreenPosition: IScreenPosition
{
.............
public virtual int X
{
get{....}
set{.....}
}
public virtual int Y
{
get{....}
set{.....}
}
..............
}
还可使用显示接口实现语法来实现属性。属性的显示实现是非公共和非虚的(因而不能被重载)。
生成自动属性
C#编译器现在能自动为属性生成代码,如下:
class Circle{
public int Radius{ get; set; }
..........
}
在这个例子中,Circle类包含名为Radius的属性。除了属性的类型,不必指定这个属性是如何工作的——get和set访问器都是空白的。C#编译器自动将这个定义转换成私有字段以及一个默认的实现,如下所示:
class Circle{
private int _radius;
public int Radius{
get{ return this. _radius;}
set{ this. _radius=value;}
}
.........
}
定义自动属性时,语法与在接口中定义属性几乎完全相同。区别在于,自动属性可以指定访问修饰符,例如private,public,或者protect。
使用属性来初始化对象
public class Triangle
{
private int side1Length = 10;
private int side2Length = 10;
private int side3Length = 10;
public int side1Length
{
set{this. side1Length = value;}
}
public int side2Length
{
set{this. side2Length = value;}
}
public int side3Length
{
set{this. side3Length = value;}
}
}
创建类的实例时,可以提供了set访问器的任何公共属性指定名称和值。如下:
Triangle tri1= new Triangle{side3Length = 15;}
Triangle tri2= new Triangle{side1Length = 15;side3Length = 20;}
Triangle tri3= new Triangle{side2Length = 15;side3Length = 20;}
Triangle tri4= new Triangle{side1Length = 15;side2Length = 15;side3Length = 20;}
这个语法称为对象初始化器。像这样调用对象初始化器,C#编译器会自动生成代码来调用默认构造器,然后调用每个具名属性的set访问器,把它初始化成指定值。还可将对象初始化器与非默认构造器配合使用。例如,假定Triangle类还有一个构造器能获取单个字符串参数(描述三角形的类型),那么可以调用这个构造器,同时对其他属性进行初始化;
Triangle tri4= new Triangle("等边三角形"){side1Length = 15;side2Length = 15;side3Length = 15;}
对象初始化器与自动属性配合使用
Polygon类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AutomaticProperties
{
class Polygon
{
public int NumSides { get; set; }
public double SideLength { get; set; }
public Polygon()
{
this.NumSides = 4;
this.SideLength = 10.0;
}
}
}
Program类
using System;
using System.Collections.Generic;
using System.Text;
namespace AutomaticProperties
{
class Program
{
static void doWork()
{
Polygon square = new Polygon();
Polygon triangle = new Polygon { NumSides = 3 };
Polygon pentagon = new Polygon { SideLength = 15.5, NumSides = 5 };
Console.WriteLine("Square: number of sides is {0}, length of each side is {1}",
square.NumSides, square.SideLength);
Console.WriteLine("Triangle: number of sides is {0}, length of each side is {1}",
triangle.NumSides, triangle.SideLength);
Console.WriteLine("Pentagon: number of sides is {0}, length of each side is {1}",
pentagon.NumSides, pentagon.SideLength);
}
static void Main(string[] args)
{
try
{
doWork();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}