Skip to content

C# - 属性

属性(Properties)是 C# 中的一个基本特性,它提供了一种灵活的机制来读取、写入或计算私有字段的值。它们是类数据对外公开的接口,也是**封装(encapsulation)**这一面向对象编程(OOP)核心原则的基石。

与直接存储数据的位置——字段(fields)不同,属性使用被称为**访问器(accessors)**的特殊方法(get 和 set)来控制对底层数据的访问。这允许你在每次访问属性时添加逻辑,例如验证或计算。

1. 自动实现的属性(现代 C# 的默认方式)

Section titled “1. 自动实现的属性(现代 C# 的默认方式)”

对于只需要获取和设置值而无需额外逻辑的简单情况,C# 提供了一种简洁的语法,称为自动实现的属性(auto-implemented properties)。编译器会自动为你生成一个私有的匿名支持字段(backing field)。

这是现代 C# 中声明属性最常见和推荐的方式。

using System;
// 一个使用自动实现属性的现代 C# 类。
public class Student
{
// 编译器为每个属性创建一个私有支持字段。
public int Id { get; set; }
public string Name { get; set; } = "Unknown"; // 可以有默认值
public string? Major { get; set; } // 可空引用类型
}
public class Program
{
public static void Main()
{
var student = new Student
{
Id = 101,
Name = "Maria",
Major = "Computer Science"
};
Console.WriteLine($"ID: {student.Id}, Name: {student.Name}, Major: {student.Major}");
}
}

当上述代码被编译并执行时,会产生以下结果:

ID: 101, Name: Maria, Major: Computer Science

2. 带有支持字段的属性(自定义逻辑)

Section titled “2. 带有支持字段的属性(自定义逻辑)”

当你需要添加逻辑时,例如验证、延迟加载或更改通知,你必须声明自己的私有**支持字段(backing field)**并显式实现 get 和/或 set 访问器。

在 set 访问器中,特殊的关键字 value 包含被赋给属性的数据。

using System;
public class Employee
{
private int _age;
public int Age
{
get
{
return _age;
}
set
{
if (value < 18)
{
throw new ArgumentException("Employee must be at least 18 years old.");
}
_age = value;
}
}
}
public class Program
{
public static void Main()
{
var emp = new Employee();
emp.Age = 25; // 这是有效的
Console.WriteLine($"Employee age: {emp.Age}");
try
{
emp.Age = 16; // 这将抛出异常
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}

此代码演示了 set 访问器中的验证,这是显式支持字段的常见用例。输出为:

Employee age: 25
Error: Employee must be at least 18 years old.

对于简单、计算型的只读属性,你可以使用一种更简洁的语法,称为表达式体属性(expression-bodied properties),它使用 =>(Lambda 箭头)符号。这是一种 get 访问器属性的语法糖。

using System;
public class Person
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
// 表达式体只读属性
public string FullName => $"{FirstName} {LastName}";
}
public class Program
{
public static void Main()
{
var person = new Person { FirstName = "John", LastName = "Doe" };
Console.WriteLine($"Full Name: {person.FullName}");
}
}

输出:

Full Name: John Doe

4. 只读和仅初始化属性(不变性)

Section titled “4. 只读和仅初始化属性(不变性)”

你可以创建不可变类型,其属性只能在对象初始化期间设置。

  • 只读属性: 只有 get 访问器的属性只能在类的构造函数中设置。
  • 仅初始化属性(Init-Only): C# 9 中引入的 init 访问器允许属性仅在对象初始化期间(使用对象初始化器)设置。这比仅通过构造函数设置提供了更大的灵活性。
using System;
public class Transaction
{
// 仅初始化属性:只能在创建时设置
public Guid TransactionId { get; init; }
public decimal Amount { get; init; }
// 带有私有 `set` 访问器的只读属性
public DateTime Timestamp { get; private set; }
public Transaction()
{
Timestamp = DateTime.UtcNow;
}
}
public class Program
{
public static void Main()
{
var tx = new Transaction
{
TransactionId = Guid.NewGuid(),
Amount = 19.99m
};
// 以下行将导致编译错误:
// tx.TransactionId = Guid.NewGuid(); // 错误:无法修改仅初始化属性
// tx.Timestamp = DateTime.Now; // 错误:私有 `set` 访问器不可访问
Console.WriteLine($"Transaction {tx.TransactionId} for {tx.Amount:C} created at {tx.Timestamp}");
}
}

一个 abstract(抽象)类可以声明 abstract 属性。这些属性在抽象类中没有实现。相反,任何继承自该抽象类的非抽象类必须使用 override 关键字提供其自己的实现。

using System;
public abstract class Shape
{
// 抽象属性:派生类必须实现它。
public abstract double Area { get; }
}
public class Square : Shape
{
public double Side { get; set; }
// 使用实现重写抽象属性。
public override double Area => Side * Side;
}
public class Circle : Shape
{
public double Radius { get; set; }
// 抽象属性的另一种实现。
public override double Area => Math.PI * Radius * Radius;
}
public class Program
{
public static void Main()
{
var square = new Square { Side = 5 };
Console.WriteLine($"Square Area: {square.Area}"); // 输出 25
var circle = new Circle { Radius = 3 };
Console.WriteLine($"Circle Area: {circle.Area:F2}"); // 输出约 28.27
}
}
  • 优先使用自动实现的属性 以保持简洁,除非你需要自定义逻辑。
  • 使用 init 访问器 来创建不可变对象,这在多线程环境中更安全,也更容易理解。
  • 使用带有支持字段的属性 用于验证、延迟初始化或在值更改时引发事件。
  • 使用表达式体属性(=>)来实现简洁、计算型的只读属性。
  • 始终为公共数据使用属性。避免使用公共字段,以维护封装性并控制类的数据。