使用属性(C# 编程指南)
属性结合了字段和方法的多个方面。 对于对象的用户来说,属性似乎是一个字段,访问属性需要相同的语法。 对于类的实现者来说,属性是一两个代码块,表示 get 访问器和/或 set 访问器。 读取属性时,将执行访问器的代码 get
块;为访问器分配值时执行访问器的代码块 set
。 将不带 set
访问器的属性视为只读。 将不带 get
访问器的属性视为只写。 将具有以上两个访问器的属性视为读写。 在 C# 9 及更高版本中,可以使用 init
访问器代替 set
访问器将属性设为只读。
与字段不同,属性不会被归类为变量。 因此,不能将属性作为 ref 或 out 参数传递。
属性具有许多用途:它们可以先验证数据,再允许进行更改;可以在类上透明地公开数据,其中数据是从某个其他源(如数据库)检索到的;可以在数据发生更改时采取措施,例如引发事件或更改其他字段的值。
通过依次指定字段的访问级别、属性类型、属性名、声明 get
访问器和/或 set
访问器的代码块,在类块中声明属性。 例如:
public class Date
{
private int _month = 7; // Backing store
public int Month
{
get => _month;
set
{
if ((value > 0) && (value < 13))
{
_month = value;
}
}
}
}
此示例中将 Month
声明为属性,以便 set
访问器可确保 Month
值设置在 1 至 12 之间。 Month
属性使用私有字段跟踪实际值。 属性数据的真实位置通常称为属性的“后备存储”,属性通常使用专用字段作为后备存储。 将字段被标记为私有,确保只能通过调用属性来对其进行更改。 有关公共和专用访问限制的详细信息,请参阅访问修饰符。
自动实现的属性为简单属性声明提供简化的语法。 有关详细信息,请参阅自动实现的属性。
get 访问器
get
访问器的正文类似于方法。 它必须返回属性类型的值。 执行 get
访问器等效于读取字段的值。 例如,在从 get
访问器返回私有变量且已启用优化时,对 get
访问器方法的调用由编译器内联,因此不存在方法调用开销。 但是,无法内联虚拟 get
访问器方法,因为编译器在编译时不知道在运行时实际可调用哪些方法。 以下示例显示一个 get
访问器,它返回私有字段 _name
的值:
class Employee
{
private string _name; // the name field
public string Name => _name; // the Name property
}
引用属性时,除了作为赋值目标外,还调用 get
访问器读取属性值。 例如:
var employee= new Employee();
//...
System.Console.Write(employee.Name); // the get accessor is invoked here
get
访问器必须以 return 或 throw 语句结尾,且控件不能超出访问器正文。
警告
使用 get
访问器更改对象的状态是一种糟糕的编程风格。
get
访问器可以用于返回字段值或计算并返回字段值。 例如:
class Manager
{
private string _name;
public string Name => _name != null ? _name : "NA";
}
在上一个代码段中,如果不给 Name
属性赋值,它将返回值 NA
。
set 访问器
set
访问器类似于返回类型为 void 的方法。 它使用名为 value
的隐式参数,该参数的类型为属性的类型。 在下面的示例中,将 set
访问器添加到 Name
属性:
class Student
{
private string _name; // the name field
public string Name // the Name property
{
get => _name;
set => _name = value;
}
}
向属性赋值时,通过使用提供新值的自变量调用 set
访问器。 例如:
var student = new Student();
student.Name = "Joe"; // the set accessor is invoked here
System.Console.Write(student.Name); // the get accessor is invoked here
为 set
访问器中的本地变量声明使用隐式参数名 value
是错误的。
init 访问器
用于创建 init
访问器的代码与用于创建 set
访问器的代码相同,只不过前者使用的关键字是 init
而不是 set
。 不同之处在于,init
访问器只能在构造函数中使用,或通过对象初始值设定项使用。
备注
可以将属性标记为 public
、private
、protected
、internal
、protected internal
或 private protected
。 这些访问修饰符定义该类的用户访问该属性的方式。 相同属性的 get
和 set
访问器可以具有不同的访问修饰符。 例如,get
可能为 public
允许从类型外部进行只读访问;而 set
可能为 private
或 protected
。 有关详细信息,请参阅访问修饰符。
可以通过使用 static
关键字将属性声明为静态属性。 静态属性可供调用方在任何时候使用,即使不存在类的任何实例。 有关详细信息,请参阅静态类和静态类成员。
可以通过使用 virtual 关键字将属性标记为虚拟属性。 虚拟属性可使派生类使用 override 关键字重写属性行为。 有关这些选项的详细信息,请参阅继承。
重写虚拟属性的属性也可以是 sealed,指定对于派生类,它不再是虚拟的。 最后,可以将属性声明为 abstract。 抽象属性不定义类中的实现,派生类必须写入自己的实现。 有关这些选项的详细信息,请参阅抽象类、密封类及类成员。
示例
此示例演示实例、静态和只读属性。 它接收通过键盘键入的员工姓名,按 1 递增 NumberOfEmployees
,并显示员工姓名和编号。
public class Employee
{
public static int NumberOfEmployees;
private static int _counter;
private string _name;
// A read-write instance property:
public string Name
{
get => _name;
set => _name = value;
}
// A read-only static property:
public static int Counter => _counter;
// A Constructor:
public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}
Hidden 属性示例
此示例演示如何访问由派生类中同名的另一属性隐藏的基类中的属性:
public class Employee
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}
public class Manager : Employee
{
private string _name;
// Notice the use of the new modifier:
public new string Name
{
get => _name;
set => _name = value + ", Manager";
}
}
class TestHiding
{
public static void Test()
{
Manager m1 = new Manager();
// Derived class property.
m1.Name = "John";
// Base class property.
((Employee)m1).Name = "Mary";
System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
}
}
/* Output:
Name in the derived class is: John, Manager
Name in the base class is: Mary
*/
以下内容是前一示例中的要点:
- 派生类中的属性
Name
隐藏基类中的属性Name
。 在这种情况下,new
修饰符用于派生类的属性声明中:public new string Name
(Employee)
转换用于访问基类中的隐藏属性:((Employee)m1).Name = "Mary";
有关隐藏成员的详细信息,请参阅 new 修饰符。
Override 属性示例
在此示例中,两个类(Cube
和 Square
)实现抽象类 Shape
,并重写其抽象 Area
属性。 请注意属性上的 override 修饰符的使用。 程序接受将边长作为输入,计算正方形和立方体的面积。 它还接受将面积作为输入,计算正方形和立方体的相应边长。
abstract class Shape
{
public abstract double Area
{
get;
set;
}
}
class Square : Shape
{
public double side;
//constructor
public Square(double s) => side = s;
public override double Area
{
get => side * side;
set => side = System.Math.Sqrt(value);
}
}
class Cube : Shape
{
public double side;
//constructor
public Cube(double s) => side = s;
public override double Area
{
get => 6 * side * side;
set => side = System.Math.Sqrt(value / 6);
}
}
class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());
// Compute the areas:
Square s = new Square(side);
Cube c = new Cube(side);
// Display the results:
System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
System.Console.WriteLine();
// Input the area:
System.Console.Write("Enter the area: ");
double area = double.Parse(System.Console.ReadLine());
// Compute the sides:
s.Area = area;
c.Area = area;
// Display the results:
System.Console.WriteLine("Side of the square = {0:F2}", s.side);
System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
}
}
/* Example Output:
Enter the side: 4
Area of the square = 16.00
Area of the cube = 96.00
Enter the area: 24
Side of the square = 4.90
Side of the cube = 2.00
*/