十、属性(Properties)

CLR #属性

📚 第十章:Properties(属性)


现在我将重新,严格按照高阶标准来总结这章内容,包括:

  • 深度提炼原理和设计动机
  • 源码层次的理解(IL / CLR层面)
  • Mermaid 图 + 丰富代码示例
  • 至少5道高阶面试题 + 精确解析

立刻开始,稍等⏳!

📚《CLR via C#》第十章 高阶总结

✨ 深度版总结:遵循你的要求(二八定律提炼 + 深度原理 + Mermaid图示 + 面试题 ≥5道)


✅ 第十章:Properties(属性)


1. 📌 什么是属性(Properties)

  • 属性(Property)本质是方法(getter/setter)的语法糖(Syntax Sugar)。
  • CLR 元数据中,属性是通过 PropertyDef 表示,但并没有直接存储值
  • 属性通过访问器方法实现:
    • get_PropertyName()
    • set_PropertyName(Type value)

🚀 底层示意(IL):

public int Age { get; set; }

对应的 IL:

.method public hidebysig specialname instance int32 get_Age() { ... }
.method public hidebysig specialname instance void set_Age(int32 'value') { ... }

注意:访问器方法都标记了 specialname,意味着编译器/工具会特殊对待。


2. 📌 自动实现属性(Auto-Implemented Properties)

  • 让编译器自动生成私有字段,减少样板代码。
  • 特点:
    • 快速定义简单的属性
    • 后期可以通过 "refactor" 手动扩展 get/set 逻辑
public string Name { get; set; }

实际上:

private string <Name>k__BackingField;
public string Name {
    get { return <Name>k__BackingField; }
    set { <Name>k__BackingField = value; }
}

3. 📌 只读属性(Read-Only Properties)

  • 仅提供 get,没有 set,使对象具备不可变性(Immutability)特征。
  • 可与 构造函数初始化配合使用。
public string Id { get; }
public MyClass(string id) => Id = id;

4. 📌 属性与字段的根本区别

比较项 属性(Property) 字段(Field)
编译后的表示 方法(get_/set_) 内存块
可控制访问 支持(如只读、延迟加载)
兼容性 支持版本兼容 直接暴露,不安全
特性附加 支持加特性 [Attribute] 支持
性能 JIT可能内联简单属性 直接访问

结论:设计类时推荐优先使用属性,除非需要暴露不可见性。


5. 📌 索引器(Indexer)

  • 本质是带参数的属性,允许对象像数组一样通过 [] 下标访问。
public string this[int index] {
    get { return elements[index]; }
    set { elements[index] = value; }
}

IL对应方法名:

  • get_Item(int)
  • set_Item(int, value)

基础示例:封装数组

public class WeekDays
{
    private string[] days = new string[7];

    // 定义索引器
    public string this[int index]
    {
        get
        {
            if (index < 0 || index >= days.Length)
                throw new IndexOutOfRangeException("Invalid day index.");
            return days[index];
        }
        set
        {
            if (index < 0 || index >= days.Length)
                throw new IndexOutOfRangeException("Invalid day index.");
            days[index] = value;
        }
    }

    public int Length => days.Length;
}

调用:

var week = new WeekDays();
week[0] = "Sunday";
week[1] = "Monday";

Console.WriteLine(week[0]); // 输出 Sunday
Console.WriteLine(week[1]); // 输出 Monday

6. 📌 可访问性控制(Access Modifiers)

  • getset 可以分别设置不同的访问级别。
public string Name { get; protected set; }
  • 应用场景:
    • 外部只能读,子类可写
    • 设计更精细化的 API

7. 📌 属性设计原则(Architectural Best Practices)

✅ 属性应该:

  • 读取快速,无副作用(不能启动复杂逻辑)
  • 计算量小(避免I/O,数据库访问)
  • 不抛出异常(除非特殊设计)

✅ 不应该:

  • 使属性get方法执行复杂任务
  • 导致长时间阻塞
  • 改变系统状态(属性访问应该是幂等(安全可重复执行)的)

8. 📌 Mermaid 图示:属性本质结构

flowchart TD A[Property Name] --> B[get_Property Name] A --> C[set_Property Namevalue] B --> D[返回内部字段] C --> E[更新内部字段]

🧠 面试题


1️⃣ 属性和字段在CLR内部有什么根本区别?

✅ 答:

  • 字段是数据,属性是方法;
  • 属性通过 get/set方法暴露,不占直接数据空间;
  • 字段直接映射内存,属性可隐藏实现细节,保证兼容性。

2️⃣ 为什么属性的访问器要用 specialname 标记?

✅ 答:

  • specialname 告诉 IL 工具和 CLR,get_X/set_X 是属性访问器;
  • 允许编译器和反射 API 自动关联到属性,而不是普通方法。

3️⃣ 自动属性的Backing Field如何命名?

✅ 答:

  • 由编译器生成,通常是 <PropertyName>k__BackingField
  • 属于 Compiler Generated Members,不应该直接访问。

4️⃣ 索引器与普通属性在 IL 层面的主要区别是什么?

✅ 答:

  • 索引器通过 get_Item / set_Item 命名;
  • 支持带参数的访问,而普通属性无参数。

5️⃣ 为什么属性访问应该是无副作用且快速的?

✅ 答:

  • 属性语义上是轻量访问(像字段一样)
  • 调试器/序列化工具会无意中触发 get,如果有副作用会破坏系统一致性。

🧹 总结表格

概念 要点
属性本质 访问器方法(get/set)的语法糖
属性 vs 字段 方法 vs 内存块
自动属性 编译器生成 Backing Field
只读属性 只提供 get,不可更改
索引器 参数化属性
设计原则 快速,无副作用,幂等
posted @ 2025-08-26 10:06  世纪末の魔术师  阅读(11)  评论(0)    收藏  举报