C# - 属性
C# - 属性
Section titled “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 Science2. 带有支持字段的属性(自定义逻辑)
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: 25Error: Employee must be at least 18 years old.3. 表达式体属性
Section titled “3. 表达式体属性”对于简单、计算型的只读属性,你可以使用一种更简洁的语法,称为表达式体属性(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 Doe4. 只读和仅初始化属性(不变性)
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}"); }}5. 抽象属性
Section titled “5. 抽象属性”一个 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 }}最佳实践和总结
Section titled “最佳实践和总结”- 优先使用自动实现的属性 以保持简洁,除非你需要自定义逻辑。
- 使用
init访问器 来创建不可变对象,这在多线程环境中更安全,也更容易理解。 - 使用带有支持字段的属性 用于验证、延迟初始化或在值更改时引发事件。
- 使用表达式体属性(
=>)来实现简洁、计算型的只读属性。 - 始终为公共数据使用属性。避免使用公共字段,以维护封装性并控制类的数据。