第8章 极简属性和表达式主体成员

第8章 极简属性和表达式主体成员

8.1 属性简史

C# 的属性随着语言的发展,其功能、语法也变得多样化。下面是它的演化:

  • C#1:支持最基础的属性写法;

    public sealed class Point
    {
        private double x, y;
        public double X { get { return x; } set { x = value; } }
        public double Y { get { return y; } set { y = value; } }
    }
    
  • C#2:允许 getter 和 setter 搭载不同的访问修饰符;

  • C#3:支持自动属性;

    public sealed class Point
    {
        public double X { get; set; }
        public double Y { get; set; }
    }
    
  • C#5:无改进

8.2 自动实现属性的升级

C#6 针对自动属性引入了两个新特性。

8.2.1 只读的自动实现属性

C#6 允许属性仅包含一个空的 getter 方法,不定义 setter 方法:

public sealed class Point
{
    public double X { get; }    // 声明只读自动
    public double Y { get; }    // 实现的属性

    public Point(double x, double y)
    {
        X = x;  // 在构造器中
        Y = y;  // 初始化属性
    }
}

8.2.2 自动实现属性的初始化

在 C#6 之前,所有自动实现属性的初始化必须通过构造器完成。C#6 对此进行了改进,可以在声明时初始化:

public class Person
{
    public List<Person> Friends { get; set; } = // 声明并初始化读/写
        new List<Person>();                     // 自动实现的属性
}

8.2.3 结构体中的自动实现属性

结构体字段赋值有两条这样的规则:

  • 在编译器确定结构体中所有字段都已经赋值之前,属性、方法、索引器和事件都是不可用的。
  • 结构体的构造器在调用返回之前必须确保所有字段都已经赋值。

在 C#6 之前,这导致“在构造器中对自动实现属性赋值,只能在链式调用另一个构造器之后进行。”:

public struct Point
{
    public double X { get; private set; }
    public double Y { get; private set; }

    public Point(double x, double y) : this()   // 链式调用默认构造器
    {
        X = x;
        Y = y;
    }
}

自 C#6 开始,语言和编译器对自动实现属性和属性对应字段之间的关系有了新的解读:

  • 允许在所有字段赋值之前为自动实现属性赋值。
  • 为自动实现属性赋值可以视为字段的初始化。
  • 只要此前自动实现属性已经赋值,那么无论其他字段是否已经完成初始化,此时都可以读取该属性。

可以把这些改进理解为:在构造器中,把自动实现属性当作 字段 对待。

上述代码改进后为:

public struct Point
{
    public double X { get; }
    public double Y { get; }

    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }
}

8.3 表达式主体成员

8.3.1 简化只读属性的计算

自 C#6 开始,简单的计算可以直接用“表达式主体成员”简化。试比较如下两种属性形式,很明显表达式体更为简洁:

public double DistanceFromOrigin
{
    get { return Math.Sqrt(X * X + Y * Y); }
}
public double DistanceFromOrigin => Math.Sqrt(X * X + Y * Y);

其中 =>​ 用于指示表达式主体成员

Notice

=>​ 在这里不是 lambda 表达式。

表达式体的常见用法有二:

  1. 传递或者代理属性;

    public struct LocalDateTime
    {
        public LocalDate Date { get; }      // 日期组件对应的属性
        public int Year => Date.Year;           //
        public int Month => Date.Month;         // 属性代理日期
        public int Day => Date.Day;             // 子组件
    
        public LocalTime TimeOfDay { get; } // 时间组件对应的属性
        public int Hour => TimeOfDay.Hour;      //
        public int Minute => TimeOfDay.Minute;  // 属性代理时间
        public int Second => TimeOfDay.Second;  // 子组件
    }
    
  2. 在另一个状态种执行简单逻辑。

    public int NanosecondOfSecond =>
        (int) (NanosecondOfDay % NodaConstants.NanosecondsPerSecond);
    

属性的使用有一条简单的原则:属性只用于简单的 计算 或对 数据 简单的访问。

8.3.2 表达式主体方法、索引器和运算符

除了表达式主体属性,还可以编写表达式主体方法、只读索引器、运算符以及自定义转换。=>​ 符号的使用方式是相同的:没有由大括号包围的表达式以及隐含的 return 语句。

下面分别是方法、运算符、索引器改为表达式主体后的样式:

public static Point Add(Point left, Vector right) => left + right;

public static Point operator+(Point left, Vector right) =>
    new Point(left.X + right.X, left.Y + right.Y);

public sealed class ReadOnlyListView<T> : IReadOnlyList<T>
{
    public T this[int index] => list[index];
    // 有省略
}

8.3.3 C#6 中表达式主体成员的限制

以下成员在 C#6 中不支持表达式主体形式,在 C#7 中得到了支持:

  • 静态构造器;
  • 终结器;
  • 实例构造器;
  • 读/写属性或只写属性;
  • 读/写索引器或只写索引器;
  • 事件。

下面这段代码是对上述成员使用表达式主体的示例,无实际含义:

public class Demo
{
    static Demo() =>                                    // 静态构造器
        Console.WriteLine("Static constructor called");
    ~Demo() => Console.WriteLine("Finalizer called");   // 终结器

    private string name;
    private readonly int[] values = new int[10];

    public Demo(string name) => this.name = name;   // 构造器

    private PropertyChangedEventHandler handler;
    public event PropertyChangedEventHandler PropertyChanged
    {
        add => handler += value;            // 使用自定义访
        remove => handler -= value;         // 问器的事件
    }

    public int this[int index]
    {
        get => values[index];           // 读/写
        set => values[index] = value;   // 索引器
    }
    public string Name
    {
        get => name;                // 读/写
        set => name = value;        // 属性
    }
}

8.3.4 表达式主体成员使用指南

没有什么好总结的,建议直接看原文

posted @ 2025-04-06 23:19  hihaojie  阅读(39)  评论(0)    收藏  举报