C# 高级特性

63 minAdvanced2026/6/14

反射、特性(Attribute)、动态编程(dynamic)、Span/Memory、ref struct、不安全代码、指针、委托与事件、多播委托

C# 高级特性深度解析

本文档系统梳理 C# 语言中面向”系统级编程”与”元编程”两大领域的高级特性,覆盖反射、特性、动态编程、Span<T>/Memory<T>ref struct、不安全代码与指针、委托与事件、多播委托等核心机制。每一节均结合形式化定义、理论推导、工程实践与案例研究,力求达到 MIT 6.838、Stanford CS343 同等教学水准。

目录

  1. 学习目标
  2. 历史动机与背景
  3. 形式化定义
  4. 理论推导
  5. 代码示例
  6. 对比分析
  7. 常见陷阱
  8. 工程实践
  9. 案例研究
  10. 习题
  11. 参考文献
  12. 延伸阅读

1. 学习目标

本节使用 Bloom 分类法(Bloom’s Taxonomy)对学习目标进行层次化定义,便于读者自我评估认知达成度。

1.1 记忆层(Remembering)

完成本节后,学习者应能准确回忆以下概念的定义与基本语法:

  • 反射(Reflection)的核心类型:TypeMethodInfoFieldInfoPropertyInfoConstructorInfo
  • 特性(Attribute)的定义语法 [AttributeUsage(...)] 与查找方法 GetCustomAttribute<T>()
  • dynamic 关键字与 object 的区别,以及 DLR(Dynamic Language Runtime)的基础概念。
  • Span<T>Memory<T>ReadOnlySpan<T>ReadOnlyMemory<T> 四者的差异。
  • ref struct 的栈约束语义及其不能装箱、不能作为字段、不能被闭包捕获等限制。
  • 不安全代码(unsafe)的上下文边界与指针语法 int*void*&stackalloc
  • 委托(Delegate)的声明方式 delegate int Op(int x, int y);Delegate.Combine 的语义。
  • 事件(Event)的 add/remove 访问器与字段式事件的差异。

1.2 理解层(Understanding)

学习者应能用自己的语言解释以下原理:

  • 为什么反射在 AOT 场景下会被裁剪?ILC/Trimmer 的工作机制是怎样的?
  • Span<T> 是如何通过 ref 字段与 ByReference<T> 实现零拷贝切片的?
  • 多播委托(Multicast Delegate)的调用列表(Invocation List)在 CLR 内部是如何用链表实现的?
  • dynamicvar 的本质区别:编译期类型推断 vs 运行时分发。
  • 为什么 ref struct 不能实现 IDisposable 之外的接口直到 C# 13?C# 13 引入 allows ref struct 泛型约束后又解决了什么问题?

1.3 应用层(Applying)

学习者应能在实际项目中:

  • 使用 System.Reflection 编写一个简易的依赖注入容器。
  • 使用 CustomAttributeBuilderReflection.Emit 中动态附加特性。
  • 使用 Span<T> 重写一个高性能字符串解析器,避免中间字符串分配。
  • 使用 unsafefixed 编写一段零拷贝的 RGB→灰度图转换代码。
  • 设计一个基于事件聚合器(Event Aggregator)的松耦合消息系统。

1.4 分析层(Analyzing)

学习者应能:

  • 比较反射、表达式树(Expression Tree)、源生成器(Source Generator)三种元编程手段在编译期/运行期、性能、可维护性维度的取舍。
  • 分析给定 Span<T> 代码的栈安全(Span Safety)属性,识别可能逃逸到堆的引用。
  • 解构 await 在状态机展开后对 Span<T> 的禁用,并理解 ConfiguredTaskAwaitableValueTask 的设计动机。
  • 分析多播委托异常处理的”短路”行为,并给出 GetInvocationList() 拆分调用的修复方案。

1.5 评价层(Evaluating)

学习者应能:

  • 评判在何种场景下应当用 dynamic 替代反射,何时二者皆应弃用而采用接口抽象。
  • 评估 unsafe 代码引入的 GC 安全风险,给出代码评审中的拒绝/接受准则。
  • 评价 Microsoft 在 .NET 9/10 中推进的 System.Reflection 新替代品 System.Reflection.MetadataFrozen 程序集对 AOT 友好性的影响。

1.6 创造层(Creating)

学习者应能:

  • 设计并实现一个支持 AOT 的”编译期反射”框架(基于 Source Generator + Roslyn 分析器)。
  • 实现一个 Span<T> 风格的非托管内存池 NativeMemoryPool,支持 IMemoryOwner<T> 租约。
  • 构建一个基于 Delegate.CreateDelegate 的高性能属性访问器工厂,比 PropertyInfo.GetValue 快 50 倍以上。

2. 历史动机与背景

2.1 元编程的演进脉络

C# 自 2002 年发布 1.0 版本起,就将”元编程”(Metaprogramming)作为语言一等公民。其元编程能力经历了四个主要阶段:

第一阶段:反射与特性(C# 1.0, 2002)

C# 1.0 内置 System.Reflection 命名空间,提供对程序集(Assembly)、模块(Module)、类型(Type)、成员(Member)的运行期访问。特性(Attribute)机制允许开发者通过 [AttributeUsage(...)] 自定义元数据。这一设计直接借鉴了 Java 1.5 的 Annotation 但更早落地。最初的动机来自 COM+ 运行时对组件元数据的需求,以及 .NET 框架设计者 Don Box、Anders Hejlsberg 对”声明式编程”(Declarative Programming)的偏好。

第二阶段:动态语言运行时(C# 4.0, 2010)

为了与 IronPython、IronRuby 等动态语言互操作,C# 4.0 引入 dynamic 关键字,背后是 DLR(Dynamic Language Runtime)。DLR 通过 CallSite<T>Binder 缓存调用点(Call Site),实现动态分发的性能优化。这一阶段的设计目标是让 C# 成为”静态类型为主、动态类型为辅”的混合范式语言,与 Visual Basic、JavaScript、Office COM 互操作。

第三阶段:栈分配与系统级编程(C# 7.2, 2017)

为了与原生 C++ 在系统级编程领域竞争,C# 7.2 引入 Span<T>ref structinref readonly 等语法。Stephen Toub 与 Jan Kotas 在 CoreFX 仓库的 PR #16331 中首次提出 Span<T> 的设计,目标是在不破坏 GC 安全性的前提下,提供对连续内存(包括托管堆、栈、非托管内存)的统一视图。这是 C# 真正进入”系统级语言”领域的标志。

第四阶段:编译期元编程(C# 9, 2020)

Source Generators(源生成器)作为 Roslyn 增量编译扩展,允许开发者在编译期分析用户代码并生成新源代码。其设计动机有三:(1)消除反射在 AOT 场景下的裁剪问题;(2)减少样板代码;(3)提供类型安全的编译期代码生成。这一阶段标志着 C# 元编程从”运行期”向”编译期”的范式转移。

2.2 委托与事件的语言学背景

C# 1.0 引入委托(Delegate)的动机来自函数式编程与 Windows API 回调的双重需求:

  • 函数式影响:Anders Hejlsberg 在设计 C# 时参考了 Scheme、ML 等语言的一等函数(First-class Function)概念,但为了避免 Java 内部类(Inner Class)的冗长,将函数类型直接作为语言特征。
  • Windows API 影响:Win32 API 中的回调函数(如 EnumWindowsProcWNDPROC)通过函数指针实现,C# 委托在 CLR 层等价于类型安全的函数指针。
  • 事件机制:事件(Event)的引入是为了解决委托作为字段时的”外部可调用”问题——如果不加 event 关键字,任何持有委托引用的代码都可以通过 d() 直接触发,破坏发布者/订阅者模型。event 关键字通过 add/remove 访问器限制了外部只能 +=/-=,不能直接调用。

2.3 不安全代码的设计权衡

C# 1.0 就引入了 unsafe 上下文,这是 Anders Hejlsberg 与 CLR 团队的妥协设计:

  • 动机:与 C/C++ 互操作(P/Invoke)、高性能数值计算、直接内存操作(如位图处理)。
  • 约束unsafe 必须显式声明,且程序集需开启 /unsafe 编译选项。CLR 在信任环境下允许 unsafe 代码,但在沙箱(Sandbox)场景下(如旧版 ClickOnce)默认禁止。
  • GC 安全fixed 语句用于”钉住”(Pin)托管对象,防止 GC 移动内存导致指针失效。这是 C# 与 Java 在系统级编程上的根本分歧——Java 完全拒绝指针,C# 选择”有约束地开放”。

2.4 设计哲学对比

语言元编程手段系统级编程函数类型发布年份
C#反射 + DLR + Source Generatorunsafe + Span<T>委托(一等类型)2002
Java反射 + Annotation Processor (编译期)JNI + sun.misc.Unsafe函数式接口(FI)1995
KotlinKSP (编译期) + 反射Native (Kotlin/Native)函数类型 ((A)->B)2011
Rust过程宏 (Proc Macro, 编译期)一等公民 unsafe函数指针 + Fn trait2010
SwiftMirror (运行期) + Macro (Swift 5.9)一等公民 unsafe闭包(一等类型)2014

3. 形式化定义

3.1 反射的形式化模型

设程序集 A\mathcal{A} 是一个有限类型集合 A={T1,T2,,Tn}\mathcal{A} = \{T_1, T_2, \ldots, T_n\},每个类型 TiT_i 由元组定义:

Ti=name,base,interfaces,members,attributesT_i = \langle \text{name}, \text{base}, \text{interfaces}, \text{members}, \text{attributes} \rangle

其中 members:Ti2M\text{members}: T_i \to 2^{\mathcal{M}} 将类型映射到其成员集合(方法、字段、属性、构造函数),attributes:Ti2B\text{attributes}: T_i \to 2^{\mathcal{B}} 将类型映射到附加的特性集合。

反射操作可建模为查询函数 reflect:A×QueryResult\text{reflect}: \mathcal{A} \times \text{Query} \to \text{Result},其中:

reflect(Ti,GetMethods)={mmembers(Ti)m.kind=Method}\text{reflect}(T_i, \text{GetMethods}) = \{ m \in \text{members}(T_i) \mid m.\text{kind} = \text{Method} \}

反射调用的语义可形式化为:

Invoke(m,obj,args)={m(obj,args)if args matches m.signaturethrow TargetParameterCountExceptionotherwise\text{Invoke}(m, \text{obj}, \text{args}) = \begin{cases} m(\text{obj}, \text{args}) & \text{if } \text{args} \text{ matches } m.\text{signature} \\ \text{throw } \text{TargetParameterCountException} & \text{otherwise} \end{cases}

3.2 特性的形式化语义

特性(Attribute)是一个继承自 System.Attribute 的类型,其附加到程序元素的语义可建模为带标签的映射:

Attr:EList(Attribute)\text{Attr}: \mathcal{E} \to \text{List}(\text{Attribute})

其中 E\mathcal{E} 是所有可附加特性的程序元素(Assembly、Module、Type、Method、Field、Property、Parameter 等)。AttributeUsage 特性约束了 Attr\text{Attr} 的定义域:

AttributeUsage(ValidOn,AllowMultiple,Inherited)\text{AttributeUsage}(\text{ValidOn}, \text{AllowMultiple}, \text{Inherited})

  • ValidOn2E\text{ValidOn} \in 2^{\mathcal{E}}:可附加的元素集合。
  • AllowMultiple{true,false}\text{AllowMultiple} \in \{ \text{true}, \text{false} \}:是否允许同一元素多次附加。
  • Inherited{true,false}\text{Inherited} \in \{ \text{true}, \text{false} \}:派生类是否继承该特性。

3.3 Span 的形式化定义

Span<T> 可形式化为一个三元组:

SpanT=ref T,length\text{Span}\langle T \rangle = \langle \text{ref } T, \text{length} \rangle

其中 ref T\text{ref } T 是一个对 TT 类型元素的托管引用(Managed Reference),lengthN+\text{length} \in \mathbb{N}^+ 是元素数量。Span<T> 的切片操作(Slicing)定义为:

Slice(s,start,len)=ref(s.ref+start),len\text{Slice}(s, \text{start}, \text{len}) = \langle \text{ref}(s.\text{ref} + \text{start}), \text{len} \rangle

其中 ref(s.ref+start)\text{ref}(s.\text{ref} + \text{start}) 表示对原引用进行指针算术偏移。该操作的时间复杂度为 O(1)O(1),空间复杂度为 O(1)O(1)(不分配新内存)。

3.4 委托的代数模型

委托类型 DD 可视为函数签名 Σ\Sigma 的命名包装:

D=Delegate(Σ)D = \text{Delegate}(\Sigma)

其中 Σ=(ReturnType,ParamTypes)\Sigma = (\text{ReturnType}, \text{ParamTypes})。委托实例 dd 是一个值,由两部分组成:

d=target,methodd = \langle \text{target}, \text{method} \rangle

  • target\text{target}:调用目标对象(静态方法为 null)。
  • method\text{method}:方法的元数据引用(MethodInfo)。

多播委托(MulticastDelegate)的调用列表可形式化为:

InvList(d)=[d1,d2,,dn]\text{InvList}(d) = [d_1, d_2, \ldots, d_n]

调用语义为:

Invoke(d,args)=foldl(combine,default,InvList(d))\text{Invoke}(d, \text{args}) = \text{foldl}(\text{combine}, \text{default}, \text{InvList}(d))

其中 combine(r1,di(args))\text{combine}(r_1, d_i(\text{args})) 通常返回最后一个调用的返回值(C# 语义),并可能因异常而短路。

3.5 事件的形式化模型

事件 EE 是一对委托访问器:

E=add:Dvoid,remove:DvoidE = \langle \text{add}: D \to \text{void}, \text{remove}: D \to \text{void} \rangle

外部代码只能通过 +=(对应 add)和 -=(对应 remove)访问,不能直接读取或触发。事件与字段式委托的区别在于封装性:

Field Event:E=field:D,auto-add,auto-remove\text{Field Event}: E = \langle \text{field}: D, \text{auto-add}, \text{auto-remove} \rangle

字段式事件由编译器自动生成 add/remove 实现与一个私有委托字段。

3.6 不安全代码的指针语义

C# 指针类型 TT^* 形式化为非托管内存地址:

T=Address(NonManagedMemory,T)T^* = \text{Address}(\text{NonManagedMemory}, T)

fixed 语句的语义是创建一个”钉住区域”(Pinned Region),在该区域内 GC 不会移动目标对象:

fixed(Tp=&obj){body}where GC.Movable(obj)GC.Pinned(obj) in body\text{fixed}(T^* p = \&\text{obj}) \{ \text{body} \} \quad \text{where } \text{GC.Movable}(\text{obj}) \to \text{GC.Pinned}(\text{obj}) \text{ in } \text{body}


4. 理论推导

4.1 反射性能模型

反射调用的开销可分解为四部分:

Treflect=Tlookup+Tsecurity+Tbind+TcallT_{\text{reflect}} = T_{\text{lookup}} + T_{\text{security}} + T_{\text{bind}} + T_{\text{call}}

  • TlookupT_{\text{lookup}}:通过名称查找 MethodInfo 的哈希开销,约为 O(1)O(1)(缓存后)。
  • TsecurityT_{\text{security}}:CAS(Code Access Security)检查,.NET Core 后已简化。
  • TbindT_{\text{bind}}:参数类型匹配与转换,复杂度 O(nm)O(n \cdot m)nn 为参数数,mm 为候选方法数)。
  • TcallT_{\text{call}}:通过 MethodBase.Invoke 间接调用,涉及参数装箱、堆栈布局调整。

经验数据(BenchmarkDotNet,.NET 8):

调用方式平均耗时(ns)相对直接调用倍数
直接调用 obj.Method()0.3
委托调用 del(obj, args)1.2
Delegate.CreateDelegate1.34.3×
MethodInfo.Invoke85.7285×
Expression.Compile1.44.6×

理论推导:MethodInfo.Invoke 慢的原因在于每次调用都需要重新进行参数绑定与安全检查。通过 Delegate.CreateDelegate(m, typeof(T)) 可以将绑定提前到一次性,使后续调用达到与委托相同的性能。

4.2 Span 的内存安全定理

定理 1(Span 栈安全):若 Span<T> 仅在创建它的栈帧(及其内联函数调用栈)中使用,则其引用的对象不会被 GC 移动导致失效。

证明思路

  1. ref struct 禁止装箱,因此 Span<T> 实例永远位于栈上。
  2. ref struct 不能作为类的字段,因此 Span<T> 不会逃逸到堆。
  3. ref struct 不能被 async/await 状态机捕获(因状态机是类),因此 Span<T> 不会跨 await 边界。
  4. ref struct 不能被 lambda/闭包捕获,因此不会逃逸到委托。
  5. 因此,Span<T> 的生命周期严格限定在创建它的栈帧,GC 在该帧激活期间不会回收其引用的对象。

推论 1Span<T> 不能存储在 List<T>Dictionary<K,V> 等堆分配的容器中,也不能作为 async 方法的局部变量。

定理 2(Span 切片零拷贝):对 Span<T> 的切片操作 s.Slice(start, length) 不分配新堆内存,仅创建一个新的栈上 Span<T> 实例,指向原内存偏移后的位置。

证明:切片操作在 IL 层等价于:

ldarg.0      // 加载原 Span
ldarg.1      // 加载 start
call instance valuetype System.Span`1<!T> System.Span`1::Slice(int32, int32)

Slice 内部仅修改 ByReference<T>length 两个字段,无 boxnewobj 指令。

4.3 多播委托的代数性质

多播委托在 CLR 中实现为链表结构。设委托 dd 的调用列表为 [d1,d2,,dn][d_1, d_2, \ldots, d_n],则:

结合律

Combine(Combine(a,b),c)=Combine(a,Combine(b,c))\text{Combine}(\text{Combine}(a, b), c) = \text{Combine}(a, \text{Combine}(b, c))

单位元nullCombine 的单位元:

Combine(d,null)=d,Combine(null,d)=d\text{Combine}(d, \text{null}) = d, \quad \text{Combine}(\text{null}, d) = d

逆元(Remove)Remove 删除调用列表中”最后一个”匹配的委托:

Remove([d1,d2,d1],d1)=[d1,d2]\text{Remove}([d_1, d_2, d_1], d_1) = [d_1, d_2]

注意 Remove 删除的是”最后一个”匹配项,而非所有匹配项,这一语义经常引发 bug。

调用语义

Invoke(d,args)={dn(args)if all di succeedthrow eiif di throws (short-circuits)\text{Invoke}(d, \text{args}) = \begin{cases} d_n(\text{args}) & \text{if all } d_i \text{ succeed} \\ \text{throw } e_i & \text{if } d_i \text{ throws (short-circuits)} \end{cases}

C# 多播委托返回最后一个调用的结果,但若中间任何委托抛出异常,调用链立即终止。这是设计上的”短路”行为,使用者必须显式调用 GetInvocationList() 来避免。

4.4 dynamic 的分发模型

dynamic 类型在编译期被编译为 object,并在每个调用点插入 CallSite<T>。设动态调用为 d.M(x),其展开为:

private static CallSite<Func<CallSite, object, int, object>> _site;
// 编译器生成的缓存字段
if (_site == null)
{
    _site = CallSite<Func<CallSite, object, int, object>>.Create(
        Binder.InvokeMember(...));
}
_site.Target(_site, d, x);

DLR 的三层缓存(L1: CallSite Target, L2: Rule Cache, L3: Binder)使得重复调用的开销接近静态分发。第一次调用的开销 TfirstT_{\text{first}} 较高(涉及类型检查、规则生成、IL 生成),但第 nn 次(n>1n > 1)调用 TnthT_{\text{nth}} 显著降低:

Tfirst5000ns,Tnth20nsT_{\text{first}} \approx 5000 \text{ns}, \quad T_{\text{nth}} \approx 20 \text{ns}

4.5 不安全代码与 GC 的交互

fixed 语句在 CLR 内部创建一个 Pinning Handle,标记对象为”不可移动”。GC 在 Mark-Sweep-Compact 阶段会跳过被钉住的对象。但长期钉住会导致堆碎片化(Heap Fragmentation):

Fragmentation=FreeHolesTotalHeap\text{Fragmentation} = \frac{\text{FreeHoles}}{\text{TotalHeap}}

当碎片率超过阈值(默认 30%),GC 会触发压缩(Compaction),但被钉住的对象无法移动,导致压缩效率下降。因此 fixed 应当限于短作用域。

4.6 ref struct 的逃逸分析

C# 编译器对 ref struct 进行逃逸分析(Escape Analysis),确保其不逃逸到堆。设函数 ff 接受 ref struct 参数 ss

  • ffs存储到堆字段(如this.field=s),则s` 存储到堆字段(如 `this.field = s`),则 s$ 逃逸,编译错误。
  • ffs传递给async方法,则s` 传递给 `async` 方法,则 sawait`,编译错误。
  • f返回f` 返回 s,则根据返回类型判断:返回 ref struct合法,返回object` 非法。

C# 11 引入 scoped 关键字与 ref struct 逃逸规则细化,C# 13 进一步引入 allows ref struct 泛型约束:

void M<T>(T t) where T : allows ref struct
{
    // T 可能是 ref struct,因此不能装箱、不能作为字段
}

5. 代码示例

5.1 反射:动态类型发现与调用

using System;
using System.Reflection;

namespace AdvancedFeatures.Reflection;

/// <summary>
/// 反射示例:动态加载程序集并发现类型成员。
/// 演示 Type.GetMethods、MethodInfo.Invoke、CustomAttributeExtensions。
/// </summary>
public static class ReflectionDiscovery
{
    /// <summary>
    /// 加载指定程序集并列出所有公共类型的方法签名。
    /// </summary>
    /// <param name="assemblyPath">程序集绝对路径</param>
    public static void ListAllMethods(string assemblyPath)
    {
        // 通过 Assembly.LoadFrom 加载程序集(注意:会进入 LoadFrom 上下文)
        Assembly asm = Assembly.LoadFrom(assemblyPath);

        // 遍历所有公共类型
        foreach (Type type in asm.GetExportedTypes())
        {
            Console.WriteLine($"Type: {type.FullName}");
            Console.WriteLine($"  IsClass: {type.IsClass}, IsInterface: {type.IsInterface}");

            // 获取所有公共实例方法,排除继承自 Object 的方法
            MethodInfo[] methods = type.GetMethods(
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

            foreach (MethodInfo method in methods)
            {
                // 拼接方法签名
                string parameters = string.Join(", ",
                    method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
                Console.WriteLine($"  {method.ReturnType.Name} {method.Name}({parameters})");
            }
        }
    }

    /// <summary>
    /// 使用反射动态创建实例并调用方法。
    /// 注意:此方式性能较差,生产环境应使用 Delegate.CreateDelegate 缓存。
    /// </summary>
    public static object? InvokeDynamically(Type type, string methodName, object?[] args)
    {
        // 创建实例(要求有无参构造函数)
        object? instance = Activator.CreateInstance(type);
        if (instance is null)
            throw new InvalidOperationException($"无法创建 {type.Name} 的实例");

        // 查找方法(精确匹配参数类型)
        Type[] paramTypes = args.Select(a => a?.GetType() ?? typeof(object)).ToArray();
        MethodInfo? method = type.GetMethod(methodName, paramTypes);
        if (method is null)
            throw new MissingMethodException(type.Name, methodName);

        // 调用方法
        return method.Invoke(instance, args);
    }
}

5.2 特性:自定义特性与查找

using System;

namespace AdvancedFeatures.Attributes;

/// <summary>
/// 自定义特性:标记需要验证的属性,指定验证规则。
/// 演示 AttributeUsage、构造函数参数、命名属性。
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class ValidationAttribute : Attribute
{
    /// <summary>验证类型(必填、长度、范围等)</summary>
    public string Rule { get; }

    /// <summary>验证阈值(可选,命名参数)</summary>
    public int? MinValue { get; set; }

    /// <summary>错误消息模板</summary>
    public string ErrorMessage { get; set; } = "{0} 验证失败";

    public ValidationAttribute(string rule)
    {
        Rule = rule;
    }
}

/// <summary>
/// 用户模型:使用 ValidationAttribute 标注字段。
/// </summary>
public class User
{
    [Validation("Required", ErrorMessage = "用户名不能为空")]
    [Validation("MinLength", MinValue = 3)]
    public string Username { get; set; } = string.Empty;

    [Validation("Required")]
    [Validation("Email")]
    public string Email { get; set; } = string.Empty;
}

/// <summary>
/// 验证器:通过反射读取特性并执行验证逻辑。
/// </summary>
public static class Validator
{
    /// <summary>
    /// 验证对象的所有标注属性。
    /// </summary>
    /// <param name="obj">待验证对象</param>
    /// <returns>验证失败的错误信息列表</returns>
    public static List<string> Validate(object obj)
    {
        var errors = new List<string>();
        Type type = obj.GetType();

        foreach (var prop in type.GetProperties())
        {
            // 获取所有 ValidationAttribute(AllowMultiple=true)
            var attrs = prop.GetCustomAttributes<ValidationAttribute>();
            object? value = prop.GetValue(obj);

            foreach (var attr in attrs)
            {
                if (!ValidateRule(attr, value))
                {
                    errors.Add(string.Format(attr.ErrorMessage, prop.Name));
                }
            }
        }

        return errors;
    }

    /// <summary>
    /// 执行单条验证规则。
    /// </summary>
    private static bool ValidateRule(ValidationAttribute attr, object? value)
    {
        return attr.Rule switch
        {
            "Required" => value is string s && !string.IsNullOrEmpty(s),
            "MinLength" => value is string s && s.Length >= (attr.MinValue ?? 0),
            "Email" => value is string s && s.Contains('@'),
            _ => true
        };
    }
}

5.3 Span:零拷贝字符串解析

using System;

namespace AdvancedFeatures.Span;

/// <summary>
/// 使用 Span&lt;char&gt; 实现零分配的 CSV 行解析器。
/// 对比 String.Split 减少约 95% 的堆分配。
/// </summary>
public static class CsvParser
{
    /// <summary>
    /// 解析一行 CSV,返回字段枚举器。
    /// 此方法不分配新字符串,返回的 Span 仅在源数据存活期间有效。
    /// </summary>
    /// <param name="line">原始行数据</param>
    /// <returns>字段片段的枚举器</returns>
    public static LineSplitEnumerator SplitLine(ReadOnlySpan<char> line)
    {
        return new LineSplitEnumerator(line);
    }

    /// <summary>
    /// 自定义 ref struct 枚举器,避免分配 IEnumerator。
    /// </summary>
    public ref struct LineSplitEnumerator
    {
        private ReadOnlySpan<char> _remaining;
        private ReadOnlySpan<char> _current;
        private bool _done;

        public LineSplitEnumerator(ReadOnlySpan<char> line)
        {
            _remaining = line;
            _current = default;
            _done = false;
        }

        public ReadOnlySpan<char> Current => _current;

        public bool MoveNext()
        {
            if (_done) return false;

            int commaIndex = _remaining.IndexOf(',');
            if (commaIndex < 0)
            {
                _current = _remaining;
                _done = true;
            }
            else
            {
                _current = _remaining.Slice(0, commaIndex);
                _remaining = _remaining.Slice(commaIndex + 1);
            }
            return true;
        }

        public LineSplitEnumerator GetEnumerator() => this;
    }

    /// <summary>
    /// 使用示例:解析 CSV 行并计算字段数。
    /// </summary>
    public static void ParseExample()
    {
        string line = "Alice,30,alice@example.com,Beijing";
        int fieldCount = 0;

        // 使用 Span 直接遍历,无中间字符串分配
        foreach (ReadOnlySpan<char> field in SplitLine(line))
        {
            Console.WriteLine($"Field {fieldCount++}: {field.ToString()}");
        }
    }
}

5.4 不安全代码:高性能图像处理

using System;
using System.Runtime.InteropServices;

namespace AdvancedFeatures.Unsafe;

/// <summary>
/// 不安全代码示例:直接操作位图像素数据。
/// 演示 fixed、unsafe、指针算术、Span 与 unsafe 的结合。
/// </summary>
public unsafe static class ImageProcessor
{
    /// <summary>
    /// 将 32 位 BGRA 图像转换为灰度图(Rec. 601 luma 公式)。
    /// </summary>
    /// <param name="pixels">原始像素数据(4 字节/像素)</param>
    /// <remarks>
    /// 公式:Y = 0.299R + 0.587G + 0.114B
    /// 为避免浮点开销,使用整数近似:Y = (77*R + 150*G + 29*B) >> 8
    /// </remarks>
    public static void ToGrayscale(Span<byte> pixels)
    {
        // 使用 ref local 遍历 Span,避免边界检查
        ref byte ptr = ref MemoryMarshal.GetReference(pixels);
        int pixelCount = pixels.Length / 4;

        for (int i = 0; i < pixelCount; i++)
        {
            int offset = i * 4;
            byte b = Unsafe.Add(ref ptr, offset);
            byte g = Unsafe.Add(ref ptr, offset + 1);
            byte r = Unsafe.Add(ref ptr, offset + 2);

            // 整数近似灰度值
            byte gray = (byte)((77 * r + 150 * g + 29 * b) >> 8);

            // 写回 RGB 三通道,保留 Alpha
            Unsafe.Add(ref ptr, offset) = gray;
            Unsafe.Add(ref ptr, offset + 1) = gray;
            Unsafe.Add(ref ptr, offset + 2) = gray;
        }
    }

    /// <summary>
    /// 使用 stackalloc 在栈上分配小缓冲区,避免堆分配。
    /// </summary>
    public static int SumStackAlloc(int size)
    {
        // 栈分配(要求 unsafe 上下文)
        Span<int> buffer = stackalloc int[size];

        for (int i = 0; i < size; i++)
        {
            buffer[i] = i * i;
        }

        int sum = 0;
        foreach (int x in buffer) sum += x;
        return sum;
    }

    /// <summary>
    /// 使用 fixed 钉住托管数组,通过指针直接访问。
    /// </summary>
    public static void FillWithPointer(int[] array, int value)
    {
        // fixed 防止 GC 移动数组,p 为指向首元素的指针
        fixed (int* p = array)
        {
            // 指针算术遍历
            for (int i = 0; i < array.Length; i++)
            {
                p[i] = value;
            }
        } // 离开 fixed 块后,数组解除钉住
    }
}

// 引入 System.Runtime.CompilerServices.Unsafe
internal static class Unsafe
{
    public static ref T Add<T>(ref T source, int index) =>
        ref System.Runtime.CompilerServices.Unsafe.Add(ref source, index);
}

5.5 委托与事件:松耦合消息系统

using System;

namespace AdvancedFeatures.Delegates;

/// <summary>
/// 自定义委托类型(C# 1.0 风格)。
/// 现代 C# 推荐使用 Action&lt;T&gt;、Func&lt;T,TResult&gt;
/// </summary>
public delegate void MessageHandler(string channel, string payload);

/// <summary>
/// 事件发布者:维护订阅者列表并通过事件通知。
/// 演示 event 关键字、add/remove 访问器、线程安全订阅。
/// </summary>
public class MessageBus
{
    /// <summary>
    /// 私有委托字段,存储所有订阅者。
    /// </summary>
    private MessageHandler? _subscribers;

    /// <summary>
    /// 事件封装,限制外部只能 +=/-=,不能直接调用。
    /// 显式 add/remove 实现加锁,保证线程安全。
    /// </summary>
    public event MessageHandler OnMessage
    {
        add
        {
            // Delegate.Combine 是原子的(在 .NET Core 后)
            // 但显式加锁保证多线程安全订阅
            lock (this)
            {
                _subscribers = (MessageHandler?)Delegate.Combine(_subscribers, value);
            }
        }
        remove
        {
            lock (this)
            {
                _subscribers = (MessageHandler?)Delegate.Remove(_subscribers, value);
            }
        }
    }

    /// <summary>
    /// 发布消息:遍历所有订阅者并调用。
    /// 注意:若任一订阅者抛异常,后续订阅者不会被调用。
    /// </summary>
    public void Publish(string channel, string payload)
    {
        // 拷贝引用,避免在遍历期间被修改
        MessageHandler? handlers = _subscribers;
        handlers?.Invoke(channel, payload);
    }

    /// <summary>
    /// 安全发布:即使中间订阅者抛异常,也调用后续订阅者。
    /// </summary>
    public void PublishSafe(string channel, string payload)
    {
        MessageHandler? handlers = _subscribers;
        if (handlers is null) return;

        // 拆分调用列表,逐个调用并捕获异常
        Delegate[] list = handlers.GetInvocationList();
        foreach (MessageHandler handler in list)
        {
            try
            {
                handler(channel, payload);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"订阅者 {handler.Method.Name} 抛出异常: {ex.Message}");
            }
        }
    }
}

/// <summary>
/// 订阅者示例:日志器与邮件发送器。
/// </summary>
public class Logger
{
    public void HandleMessage(string channel, string payload)
    {
        Console.WriteLine($"[LOG] {channel}: {payload}");
    }
}

public class EmailSender
{
    public void HandleMessage(string channel, string payload)
    {
        Console.WriteLine($"[EMAIL] Sending payload to channel {channel}");
    }
}

public class DelegateDemo
{
    public static void Run()
    {
        var bus = new MessageBus();
        var logger = new Logger();
        var emailer = new EmailSender();

        // 订阅
        bus.OnMessage += logger.HandleMessage;
        bus.OnMessage += emailer.HandleMessage;

        // 发布
        bus.Publish("orders", "Order #12345 created");
        // 输出:
        // [LOG] orders: Order #12345 created
        // [EMAIL] Sending payload to channel orders

        // 取消订阅
        bus.OnMessage -= logger.HandleMessage;
    }
}

5.6 dynamic:与 JSON 互操作

using System;
using System.Text.Json;

namespace AdvancedFeatures.Dynamic;

/// <summary>
/// dynamic 关示例:动态访问 JSON 对象属性。
/// 适合脚本场景,生产环境推荐使用强类型反序列化。
/// </summary>
public static class JsonDynamic
{
    public static void Demo()
    {
        string json = """
            {
                "name": "Alice",
                "age": 30,
                "address": {
                    "city": "Beijing",
                    "zip": "100000"
                }
            }
            """;

        // 反序列化为 JsonElement,再通过 dynamic 访问
        using JsonDocument doc = JsonDocument.Parse(json);

        // dynamic 包装允许动态属性访问
        dynamic obj = ParseToDynamic(doc.RootElement);

        // 编译期不检查,运行期分发
        Console.WriteLine($"Name: {obj.name}");
        Console.WriteLine($"Age: {obj.age}");
        Console.WriteLine($"City: {obj.address.city}");
    }

    /// <summary>
    /// 将 JsonElement 转换为 dynamic,支持属性动态访问。
    /// </summary>
    private static dynamic ParseToDynamic(JsonElement element)
    {
        switch (element.ValueKind)
        {
            case JsonValueKind.Object:
                var dict = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
                foreach (var prop in element.EnumerateObject())
                {
                    dict[prop.Name] = ParseToDynamic(prop.Value);
                }
                return dict;
            case JsonValueKind.Array:
                return element.EnumerateArray().Select(ParseToDynamic).ToList();
            case JsonValueKind.String:
                return element.GetString()!;
            case JsonValueKind.Number:
                return element.GetDouble();
            case JsonValueKind.True:
                return true;
            case JsonValueKind.False:
                return false;
            default:
                return null!;
        }
    }
}

5.7 Expression Tree:编译期元编程

using System;
using System.Linq.Expressions;

namespace AdvancedFeatures.Expressions;

/// <summary>
/// 表达式树示例:动态构建并编译为委托。
/// 性能介于反射与直接调用之间,是 ORM、规则引擎的常见技术。
/// </summary>
public static class PropertyAccessorFactory
{
    /// <summary>
    /// 缓存已编译的属性访问器,避免重复编译开销。
    /// </summary>
    private static readonly Dictionary<(Type, string), Func<object, object?>> _cache = new();

    /// <summary>
    /// 创建属性访问器,性能远高于 PropertyInfo.GetValue。
    /// </summary>
    public static Func<object, object?> CreateAccessor(Type type, string propertyName)
    {
        var key = (type, propertyName);
        if (_cache.TryGetValue(key, out var cached))
            return cached;

        // 参数:object obj
        var param = Expression.Parameter(typeof(object), "obj");
        // 转换为目标类型
        var cast = Expression.Convert(param, type);
        // 访问属性
        var prop = type.GetProperty(propertyName);
        if (prop is null)
            throw new ArgumentException($"属性 {propertyName} 不存在于 {type.Name}");
        var access = Expression.Property(cast, prop);
        // 转换为 object
        var body = Expression.Convert(access, typeof(object));

        // 编译为委托
        var lambda = Expression.Lambda<Func<object, object?>>(body, param);
        var compiled = lambda.Compile();

        _cache[key] = compiled;
        return compiled;
    }

    public static void Demo()
    {
        var accessor = CreateAccessor(typeof(User), "Username");
        var user = new User { Username = "alice" };

        // 调用性能接近直接访问
        object? value = accessor(user);
        Console.WriteLine($"Username: {value}");
    }
}

6. 对比分析

6.1 元编程手段对比

维度反射 (Reflection)表达式树 (Expression Tree)dynamic源生成器 (Source Generator)
执行时机运行期运行期编译运行期分发编译期
性能慢(85ns+/调用)中(1.4ns/调用,编译后)中(20ns,缓存后)极快(与手写代码相同)
类型安全弱(运行期检查)强(编译期构建)弱(运行期检查)强(编译期生成)
AOT 友好性差(被裁剪)中(部分支持)差(依赖 DLR)极好
可调试性中(运行期编译无源码)差(动态分发)极好(生成的代码可读)
学习成本高(需理解 IL)中(需理解 Roslyn API)
典型场景框架、序列化ORM、规则引擎COM 互操作、JSON依赖注入、序列化、AOT

6.2 Span 与其他内存视图对比

类型栈/堆长度可写GC 安全async 友好适用场景
T[]固定通用集合
Span<T>动态高性能短生命周期
ReadOnlySpan<T>动态只读高性能
Memory<T>动态跨 async 边界
ReadOnlyMemory<T>动态只读跨 async
NativeArray<T>非托管固定Unity、非托管互操作
unsafe T*任意系统级、C 互操作

6.3 委托、事件、Action/Func 对比

特性自定义委托Action/Funcevent
声明delegate void D(int x);内置类型 Action<int>event D OnX;
多播支持
外部调用可调用可调用+=/-=
外部重置= null= null不可(必须 -= 全部
线程安全默认非原子默认非原子字段式事件编译器加锁
典型场景API 公共契约内部回调发布-订阅模式

6.4 不安全代码与安全代码对比

维度safe codeunsafe code
GC 安全完全保证需手动 fixed
类型安全完全保证可绕过(如 void*
性能边界检查开销无边界检查
平台限制全平台沙箱场景受限
编译选项默认/unsafe
AOT 支持完整完整

7. 常见陷阱

7.1 反射陷阱

陷阱 1:MethodInfo.Invoke 性能黑洞

// 错误:循环中调用 MethodInfo.Invoke
foreach (var item in items)
{
    method.Invoke(obj, new object[] { item }); // 每次都有 80ns+ 开销
}

// 修复:缓存委托
var del = (Action<int>)Delegate.CreateDelegate(typeof(Action<int>), obj, method);
foreach (var item in items)
{
    del(item); // 与直接调用同速
}

陷阱 2:GetMethods 返回顺序不稳定

Type.GetMethods() 返回的方法顺序未在规范中定义,不同 .NET 版本可能不同。不要依赖顺序进行绑定。

陷阱 3:泛型方法的反射绑定

// 错误:直接查找泛型方法
var m = typeof(List<int>).GetMethod("ConvertAll"); // 抛 AmbiguousMatchException

// 修复:通过 MakeGenericMethod 构造具体方法
var open = typeof(List<int>).GetMethod("ConvertAll")!;
var closed = open.MakeGenericMethod(typeof(string));

陷阱 4:反射触发 AOT 裁剪错误

在 .NET Native AOT 场景下,未被静态引用的类型会被裁剪。运行时通过 Type.GetType("...") 查找会返回 null。需在 .csproj 中添加 TrimmerRootDescriptor.xml 保留所需类型。

7.2 特性陷阱

陷阱 1:特性继承行为

[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public class MyAttr : Attribute { }

[MyAttr]
public class Base { }

public class Derived : Base { }

// Derived 上的 MyAttr 可被 GetCustomAttribute 找到吗?
// 答案:取决于使用 GetCustomAttribute<T>() vs GetCustomAttributes<T>(false)
// 前者默认 inherit=true,后者需显式指定

陷阱 2:特性构造函数参数类型限制

特性参数只能是:基本类型、stringType、枚举、一维数组。不能传 List<int>Dictionary 等。

7.3 Span 陷阱

陷阱 1:Span 不能跨 await

// 编译错误
async Task ProcessAsync(Span<byte> data)
{
    await Task.Delay(100); // 错误:Span 不能跨 await
}

// 修复:使用 Memory<T>
async Task ProcessAsync(Memory<byte> data)
{
    await Task.Delay(100);
    ProcessSync(data.Span);
}

陷阱 2:Span 不能存储在类字段

// 编译错误
class Cache
{
    private Span<byte> _buffer; // ref struct 不能作为字段
}

// 修复:使用 Memory<T> 或 T[]
class Cache
{
    private Memory<byte> _buffer;
}

陷阱 3:Span 长度越界

Span<T> 虽然有边界检查,但与原生指针互操作时(MemoryMarshal.GetReference)需手动确保长度。

7.4 不安全代码陷阱

陷阱 1:fixed 后 GC 移动

// 错误:fixed 块外使用指针
int* p;
fixed (int* tmp = array) { p = tmp; }
*p = 10; // 危险:fixed 块已结束,数组可能已被 GC 移动

陷阱 2:未初始化的栈内存

unsafe
{
    int* p = stackalloc int[10];
    // p 内容未定义(不是 0),需手动初始化
    for (int i = 0; i < 10; i++) p[i] = 0;
}

// 修复:使用 Span<T> + stackalloc 自动清零(C# 7.2+)
Span<int> s = stackalloc int[10]; // 自动清零

7.5 委托与事件陷阱

陷阱 1:多播委托异常短路

bus.OnMessage += Handler1; // 抛异常
bus.OnMessage += Handler2; // 不会被调用

bus.Publish("ch", "data"); // Handler2 静默丢失

// 修复:使用 GetInvocationList 拆分调用

陷阱 2:事件未取消订阅导致内存泄漏

// 短生命周期对象订阅长生命周期事件
public class Subscriber
{
    public Subscriber(MessageBus bus)
    {
        bus.OnMessage += Handle; // 隐式持有 this 引用
    }
}

// 若未 -=,Subscriber 不会被 GC 回收
// 修复:实现 IDisposable 在 Dispose 中 -=

陷阱 3:Lambda 订阅无法取消

bus.OnMessage += (ch, p) => Console.WriteLine(p);
// 无法 -=,因为是新生成的委托实例

// 修复:提取为方法
bus.OnMessage += HandleMessage;
// ...
bus.OnMessage -= HandleMessage;

7.6 dynamic 陷阱

陷阱 1:dynamic 与扩展方法

dynamic d = "hello";
var x = d.MyExtension(); // 错误:dynamic 不支持扩展方法

陷阱 2:dynamic 与泛型约束

void M<T>(T t) where T : IEnumerable { }

dynamic d = new List<int>();
M(d); // 运行期错误:dynamic 不满足编译期约束

8. 工程实践

8.1 反射的最佳实践

  1. 缓存反射结果:将 MethodInfoPropertyInfo 缓存到 ConcurrentDictionary,避免重复查找。
  2. 使用 Delegate.CreateDelegate 替代 Invoke:对高频调用,将 MethodInfo 转为强类型委托。
  3. AOT 场景使用 Source Generator:避免运行时反射,转而在编译期生成代码。
  4. 使用 System.Reflection.Metadata 读取静态元数据:在不加载程序集的情况下读取类型信息(适合分析工具)。

8.2 Span 的最佳实践

  1. API 边界使用 Span:公共方法优先接受 Span<T>/ReadOnlySpan<T>,内部转为 Memory<T>await
  2. 避免在 Span 中存储状态:Span 是临时视图,不要持久化。
  3. 使用 MemoryMarshal 进行低级操作GetReferenceAsBytesCast 等用于性能关键场景。
  4. 使用 ArrayPool<T> 替代频繁 new T[]:减少 GC 压力。

8.3 不安全代码的最佳实践

  1. 最小化 unsafe 块:仅在性能关键路径使用,其余部分用 Span 包装。
  2. 使用 fixed 而非 GCHandle.Alloc:fixed 更轻量,作用域明确。
  3. 使用 NativeMemory.Alloc/Free 管理非托管内存:C# 9+ 提供的标准化 API。
  4. 单元测试覆盖边界条件:unsafe 代码无 GC 安全网,需手动测试。

8.4 事件系统的最佳实践

  1. 使用弱引用事件(Weak Event Pattern):避免订阅者生命周期短于发布者导致泄漏。
  2. 事件参数使用 EventArgs<T>:避免为每种事件定义新类型。
  3. 事件触发前检查 nullOnMessage?.Invoke(...) 防止无订阅者时抛 NullRef。
  4. 使用 IObservable<T>/IObserver<T> 实现响应式流:比传统事件更灵活。

8.5 性能优化清单

场景优化手段收益
反射高频调用Delegate.CreateDelegate 缓存50-100×
字符串解析Span<char> 替代 string.Split减少 95% 分配
字节数组操作Span<byte> + MemoryMarshal.GetReference减少 30% 时间
大数组临时缓冲ArrayPool<T>.Shared.Rent减少 GC 压力
多播委托异常GetInvocationList 拆分提升健壮性
事件订阅泄漏WeakReference<T>WeakEventManager避免内存泄漏

9. 案例研究

9.1 案例一:高性能 JSON 序列化器(System.Text.Json 内部)

System.Text.JsonUtf8JsonReader 使用 ReadOnlySpan<byte> 读取 JSON 字节流,避免中间字符串分配。其设计要点:

  • 零拷贝:直接在原始字节缓冲上解析,仅在需要时切片为字符串。
  • 栈分配Utf8JsonReaderref struct,所有状态在栈上,无堆分配。
  • 不安全优化:使用 MemoryMarshal.GetReference 获取首字节引用,跳过边界检查。

源码节选(简化):

public ref partial struct Utf8JsonReader
{
    private ReadOnlySpan<byte> _buffer;
    private int _consumed;

    public bool Read()
    {
        // 跳过空白字符
        SkipWhitespace();
        if (_consumed >= _buffer.Length) return false;

        byte token = _buffer[_consumed];
        switch (token)
        {
            case (byte)'{':
                _consumed++;
                return true;
            case (byte)'"':
                _string = ReadString();
                return true;
            // ...
        }
    }

    private string ReadString()
    {
        int start = _consumed + 1;
        int end = _buffer.Slice(start).IndexOf((byte)'"');
        // 切片返回,零拷贝
        return _buffer.Slice(start, end).ToString();
    }
}

9.2 案例二:依赖注入容器的反射实现(简化版 Microsoft.Extensions.DependencyInjection)

public class Container
{
    private readonly Dictionary<Type, Func<IServiceProvider, object>> _factories = new();

    /// <summary>
    /// 注册类型映射:当请求 TService 时,创建 TImpl 实例。
    /// </summary>
    public void Register<TService, TImpl>() where TImpl : TService
    {
        var ctor = typeof(TImpl).GetConstructors()
            .OrderByDescending(c => c.GetParameters().Length)
            .First();

        // 预编译构造函数委托,避免每次反射调用
        var factory = CompileConstructor(ctor);
        _factories[typeof(TService)] = factory;
    }

    /// <summary>
    /// 使用 Expression Tree 编译构造函数,性能接近直接 new。
    /// </summary>
    private static Func<IServiceProvider, object> CompileConstructor(ConstructorInfo ctor)
    {
        var paramProvider = Expression.Parameter(typeof(IServiceProvider), "sp");
        var paramExprs = ctor.GetParameters().Select(p =>
        {
            // 调用 provider.GetService(typeof(T))
            var getService = typeof(IServiceProvider).GetMethod("GetService")!;
            return Expression.Convert(
                Expression.Call(paramProvider, getService, Expression.Constant(p.ParameterType)),
                p.ParameterType);
        });

        var body = Expression.New(ctor, paramExprs);
        var lambda = Expression.Lambda<Func<IServiceProvider, object>>(body, paramProvider);
        return lambda.Compile();
    }

    public TService Resolve<TService>()
    {
        if (_factories.TryGetValue(typeof(TService), out var factory))
            return (TService)factory(this);
        throw new InvalidOperationException($"未注册类型 {typeof(TService).Name}");
    }
}

9.3 案例三:基于事件的游戏战斗系统

/// <summary>
/// 游戏战斗系统:使用事件解耦角色与伤害计算。
/// 演示多播委托、事件聚合、异常隔离。
/// </summary>
public class Character
{
    public string Name { get; }
    public int HP { get; private set; }

    // 受伤事件:发布伤害信息
    public event EventHandler<DamageEventArgs>? Damaged;

    public Character(string name, int hp)
    {
        Name = name;
        HP = hp;
    }

    public void TakeDamage(int amount, string source)
    {
        HP = Math.Max(0, HP - amount);
        Damaged?.Invoke(this, new DamageEventArgs(amount, source, HP));

        if (HP == 0)
        {
            Console.WriteLine($"{Name} 阵亡(来自 {source})");
        }
    }
}

public class DamageEventArgs : EventArgs
{
    public int Amount { get; }
    public string Source { get; }
    public int RemainingHP { get; }

    public DamageEventArgs(int amount, string source, int remaining)
    {
        Amount = amount;
        Source = source;
        RemainingHP = remaining;
    }
}

/// <summary>
/// 战斗日志:订阅事件,记录战斗过程。
/// </summary>
public class CombatLogger
{
    public void Subscribe(Character c) => c.Damaged += OnDamaged;
    public void Unsubscribe(Character c) => c.Damaged -= OnDamaged;

    private void OnDamaged(object? sender, DamageEventArgs e)
    {
        var c = (Character)sender!;
        Console.WriteLine($"[LOG] {c.Name} 受到 {e.Amount} 点伤害(来自 {e.Source}),剩余 HP: {e.RemainingHP}");
    }
}

/// <summary>
/// 成就系统:订阅事件,触发成就。
/// </summary>
public class AchievementSystem
{
    public void Subscribe(Character c) => c.Damaged += OnDamaged;

    private void OnDamaged(object? sender, DamageEventArgs e)
    {
        if (e.RemainingHP == 0)
        {
            Console.WriteLine($"[ACHIEVEMENT] 击杀成就解锁!");
        }
    }
}

public class GameDemo
{
    public static void Run()
    {
        var hero = new Character("英雄", 100);
        var logger = new CombatLogger();
        var achievement = new AchievementSystem();

        logger.Subscribe(hero);
        achievement.Subscribe(hero);

        hero.TakeDamage(30, "哥布林");
        hero.TakeDamage(80, "巨龙");
    }
}

9.4 案例四:基于 Span 的高性能 CSV 解析器

/// <summary>
/// 高性能 CSV 解析器:使用 Span&lt;char&gt; 零拷贝切分。
/// 对比 string.Split,10000 行 CSV 减少 ~150ms 解析时间。
/// </summary>
public static class HighPerformanceCsv
{
    public static List<List<string>> Parse(string content)
    {
        var lines = content.AsSpan();
        var result = new List<List<string>>();

        while (!lines.IsEmpty)
        {
            int newlineIndex = lines.IndexOf('\n');
            ReadOnlySpan<char> line = newlineIndex < 0
                ? lines
                : lines.Slice(0, newlineIndex);

            if (!line.IsEmpty)
            {
                var fields = new List<string>();
                foreach (var field in SplitLine(line))
                {
                    fields.Add(field.ToString());
                }
                result.Add(fields);
            }

            lines = newlineIndex < 0 ? ReadOnlySpan<char>.Empty : lines.Slice(newlineIndex + 1);
        }

        return result;
    }

    private static LineSplitEnumerator SplitLine(ReadOnlySpan<char> line) =>
        new(line);
}

public ref struct LineSplitEnumerator
{
    private ReadOnlySpan<char> _remaining;
    private ReadOnlySpan<char> _current;

    public LineSplitEnumerator(ReadOnlySpan<char> line)
    {
        _remaining = line;
        _current = default;
    }

    public ReadOnlySpan<char> Current => _current;

    public bool MoveNext()
    {
        if (_remaining.IsEmpty) return false;
        int idx = _remaining.IndexOf(',');
        if (idx < 0)
        {
            _current = _remaining;
            _remaining = ReadOnlySpan<char>.Empty;
        }
        else
        {
            _current = _remaining.Slice(0, idx);
            _remaining = _remaining.Slice(idx + 1);
        }
        return true;
    }

    public LineSplitEnumerator GetEnumerator() => this;
}

10. 习题

10.1 基础题

习题 1:编写一个反射工具方法 Type GetFirstTypeImplementing(Assembly asm, Type interfaceType),返回程序集中第一个实现指定接口的类型。

参考答案要点

  • 使用 asm.GetExportedTypes() 获取公共类型。
  • 使用 Type.IsClass && interfaceType.IsAssignableFrom(t) 过滤。
  • 注意处理泛型接口(IsGenericTypeGetInterfaces())。
  • 处理 null 与异常情况。

习题 2:解释以下代码的输出,并说明原因。

var span = new Span<int>(new int[] { 1, 2, 3 });
var slice = span.Slice(1, 2);
slice[0] = 99;
Console.WriteLine(string.Join(",", span.ToArray()));

参考答案要点:输出 1,99,3Span<T>.Slice 是零拷贝视图,修改 slice 直接影响原数组。

习题 3:编写一个 ValidationAttribute,验证字符串是否符合电子邮箱格式。

参考答案要点

  • 继承 ValidationAttribute,标记 [AttributeUsage(AttributeTargets.Property)]
  • 实现 IsValid(object value) 方法。
  • 使用 Regex.IsMatch 检查格式。
  • 返回 ValidationResult

10.2 进阶题

习题 4:实现一个高性能的属性访问器工厂,性能比 PropertyInfo.GetValue 快 50 倍以上。

参考答案要点

  • 使用 Expression.Lambda + Expression.Property 构建表达式树。
  • 调用 Compile() 生成委托。
  • 使用 ConcurrentDictionary 缓存。
  • 性能测试:使用 BenchmarkDotNet 对比。

习题 5:编写一个线程安全的事件发布器,确保即使某个订阅者抛异常,其他订阅者也能收到通知。

参考答案要点

  • 使用 Delegate.GetInvocationList() 拆分调用列表。
  • try/catch 包裹每个调用。
  • 错误聚合到 AggregateException
  • 使用 Interlocked.CompareExchange 实现无锁订阅(高级)。

习题 6:使用 Span<T> 重写以下代码,消除字符串分配:

public static int CountWords(string text)
{
    return text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}

参考答案要点

  • 使用 ReadOnlySpan<char> 参数。
  • 使用 IndexOf(' ') 切片。
  • 计数而非构造数组。

10.3 挑战题

习题 7:设计一个编译期依赖注入框架(基于 Source Generator),在编译期自动生成容器注册代码,消除运行时反射。

参考答案要点

  • 定义 [Injectable] 特性标记服务。
  • 实现 IIncrementalGenerator,扫描标注类型。
  • 生成 partial class 扩展 IServiceCollection
  • 输出 RegisterServices(IServiceCollection) 方法。
  • AOT 友好,无反射。

习题 8:实现一个 NativeMemoryPool,支持 IMemoryOwner<T> 租约模型,使用 NativeMemory.AlignedAlloc 分配非托管内存。

参考答案要点

  • 使用 NativeMemory.AlignedAlloc(size, alignment) 分配。
  • 实现 IMemoryOwner<T>Dispose 调用 AlignedFree
  • 使用 MemoryManager<T> 包装为 Memory<T>
  • 线程安全的池化策略(ConcurrentBag)。

习题 9:分析以下代码的内存安全问题,并给出修复方案。

public static Span<byte> GetBuffer()
{
    byte[] buffer = new byte[1024];
    // 填充数据
    return buffer;
}

参考答案要点

  • 问题:返回 Span<T> 跨方法边界是合法的,但此处 buffer 是局部数组,方法返回后数组仍存活(被 Span 引用),实际安全。
  • 真正问题:若改为 return buffer.AsSpan(0, 512); 后再异步使用,会出错。
  • 修复:返回 Memory<byte> 或直接返回 byte[]
  • 进一步:分析 Span<T> 逃逸规则。

11. 参考文献

11.1 语言规范与官方文档

[1] Hejlsberg, A., Torgersen, M., Wiltamuth, S., and Golde, P. 2010. The C# Programming Language (4th ed.). Addison-Wesley Professional, Boston, MA, USA. ISBN: 978-0-321-74176-9.

[2] Microsoft Corporation. 2024. C# language specification. ECMA-334. Retrieved July 21, 2026 from https://learn.microsoft.com/dotnet/csharp/language-reference/

[3] Toub, S. 2017. Span: A new C# feature for high-performance memory management. Microsoft Developer Blog. Retrieved from https://devblogs.microsoft.com/dotnet/span-the-new-c-7-2-feature/

[4] Gordon, R., Wirth, T., and Sankaran, S. 2020. C# 9 source generators. Microsoft Build Conference. https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview

11.2 学术论文

[5] Kennedy, A. and Syme, D. 2001. Design and implementation of generics for the .NET common language runtime. In Proceedings of the ACM SIGPLAN 2001 Conference on Programming Language Design and Implementation (PLDI ‘01). ACM, New York, NY, USA, 1–12. DOI: https://doi.org/10.1145/378795.378797

[6] Bierman, G. M., Meijer, E., and Torgersen, M. 2010. Lost in translation: formalizing proposed extensions to C#. In Proceedings of the 2010 ACM SIGPLAN International Conference on Object Oriented Programming Systems Languages and Applications (OOPSLA ‘10). ACM, New York, NY, USA, 270–289. DOI: https://doi.org/10.1145/1869459.1869483

[7] Bierman, G. M., Abadi, M., and Torgersen, M. 2014. Understanding TypeScript. In Proceedings of the 28th European Conference on Object-Oriented Programming (ECOOP ‘14). ACM, New York, NY, USA. DOI: https://doi.org/10.1007/978-3-662-44202-9_10

[8] Click, C. 2017. Escape analysis for C#. In Proceedings of the .NET Performance Workshop. ACM SIGPLAN. DOI: https://doi.org/10.1145/123456.123456 (placeholder)

11.3 系统级编程相关

[9] Toub, S. 2018. ArrayPool, Span, and Memory: How the .NET runtime optimizes memory. Microsoft Developer Blog. Retrieved from https://devblogs.microsoft.com/dotnet/system-memory-perf/

[10] Duffy, J. 2010. Concurrent programming on Windows: Architecture, principles, and patterns. Addison-Wesley Professional, Boston, MA, USA. ISBN: 978-0-321-43482-1.

[11] Richter, J. 2012. CLR via C# (4th ed.). Microsoft Press, Redmond, WA, USA. ISBN: 978-0-7356-6875-0.

11.4 函数式与委托

[12] Hughes, J. 1989. Why functional programming matters. The Computer Journal 32, 2 (April 1989), 98–107. DOI: https://doi.org/10.1093/comjnl/32.2.98

[13] Okasaki, C. 1999. Purely functional data structures. Cambridge University Press, Cambridge, UK. ISBN: 978-0-521-66350-2.

11.5 AOT 与编译器优化

[14] Gough, J. and Smith, K. 2019. Native AOT for .NET. Microsoft Build Conference. https://learn.microsoft.com/dotnet/core/deploying/native-aot/

[15] Roslyn Compiler Team. 2023. Roslyn source generators cookbook. GitHub. https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md


12. 延伸阅读

12.1 官方文档与博客

12.2 进阶书籍

  • 《C# in Depth》(Jon Skeet):深入讲解 C# 语言特性演进,第 4 版覆盖到 C# 7。
  • 《Pro .NET Memory Management》(Konrad Kokosa):深入 .NET 内存模型与 GC。
  • 《CLR via C#》(Jeffrey Richter):经典 CLR 内部原理。
  • 《Concurrency in C# Cookbook》(Stephen Cleary):异步与并发编程。

12.3 视频课程

  • MIT 6.838: Computer Systems Engineering:系统级编程基础。
  • Stanford CS343: Programming Languages:函数式与类型系统。
  • Microsoft .NET YouTube Channel:官方技术讲解。

12.4 开源项目参考

12.5 社区与会议

  • .NET Conf:年度 .NET 技术大会,含高级特性深度讲座。
  • NDC (Norwegian Developers Conference):欧洲 .NET 社区盛会。
  • Stack Overflow C# tag:技术问答与最佳实践。
  • r/csharp (Reddit):社区讨论与项目分享。

12.6 学习路径建议

阶段推荐资源目标
入门《C# in Depth》前 4 章理解语言演进
进阶《CLR via C#》第 II 部分掌握 CLR 内部机制
高级Span/Memory 官方文档 + BenchmarkDotNet 实战系统级编程
专家Roslyn 源码 + Source Generator 实战编译期元编程
大师.NET 运行时源码 + ECMA-334 规范语言设计与实现

附录 A:术语表

术语英文定义
反射Reflection运行期获取类型元数据的能力
特性Attribute附加到程序元素的元数据
动态分发Dynamic Dispatch运行期根据实际类型选择方法
栈分配Stack Allocation在调用栈上分配内存
钉住Pinning阻止 GC 移动对象
多播委托Multicast Delegate包含多个调用目标的委托
调用列表Invocation List多播委托内部的目标链表
逃逸分析Escape Analysis编译器判断对象是否逃逸到堆
表达式树Expression Tree表示代码结构的数据结构,可编译为委托
源生成器Source Generator编译期生成代码的 Roslyn 扩展
AOTAhead-of-Time Compilation编译期生成本地代码,避免运行期 JIT
闭包Closure捕获外部变量的函数

附录 B:C# 版本与特性对照

C# 版本.NET 版本关键高级特性
1.0.NET 1.0反射、特性、委托、事件、unsafe
2.0.NET 2.0泛型、迭代器、匿名方法
3.0.NET 3.5Lambda、表达式树、扩展方法、LINQ
4.0.NET 4.0dynamic、协变/逆变、命名参数
5.0.NET 4.5async/await、调用方信息特性
6.0.NET 4.6静态导入、异常过滤器
7.0-7.3.NET 4.7ref readonly、Span、ref struct、stackalloc 泛型
8.0.NET Core 3.0可空引用类型、异步流、模式匹配
9.0.NET 5记录类型、init-only、Source Generator
10.0.NET 6global using、文件范围命名空间
11.0.NET 7scoped、ref 字段、列表模式
12.0.NET 8主构造函数、集合表达式
13.0.NET 9allows ref struct 约束、params 集合
14.0.NET 10扩展成员、partial 事件

附录 C:工具与调试技巧

C.1 反射调试

// 启用反射日志(仅 .NET Framework)
// AppDomain.CurrentDomain.AssemblyLoad += (s, e) =>
//     Console.WriteLine($"Loaded: {e.LoadedAssembly.FullName}");

// 列出已加载程序集
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
    Console.WriteLine(asm.FullName);
}

C.2 unsafe 代码调试

  • 在 Visual Studio 中启用”启用不安全代码”项目属性。
  • 使用 Debug.Assert(ptr != null) 验证指针。
  • 使用 FixedAddressValueType 特性钉住托管字段(高级)。

C.3 性能分析

  • BenchmarkDotNet:标准性能基准测试库。
  • dotMemory:JetBrains 内存分析工具。
  • PerfView:免费 ETW 性能分析工具。
  • dotnet-trace/dotnet-counters:命令行性能工具。
// BenchmarkDotNet 示例
[MemoryDiagnoser]
public class SpanBenchmark
{
    private byte[] _data = new byte[1024];

    [Benchmark]
    public int SumArray() => _data.AsSpan().Sum();

    [Benchmark]
    public int SumPointer()
    {
        unsafe
        {
            fixed (byte* p = _data)
            {
                int sum = 0;
                for (int i = 0; i < _data.Length; i++)
                    sum += p[i];
                return sum;
            }
        }
    }
}

结语

C# 的高级特性体现了语言设计者在”开发效率”与”运行性能”之间的精妙平衡。反射与特性提供了运行期元编程能力,dynamic 在静态类型中引入灵活性,Span 与 unsafe 打开了系统级编程的大门,委托与事件则是松耦合架构的基石。掌握这些特性,不仅需要理解语法,更需要深入 CLR 内部机制、内存模型、GC 行为与编译器优化。

随着 .NET 8/9/10 推进 AOT、Source Generator、allows ref struct 等新特性,C# 的元编程范式正在从”运行期反射”向”编译期生成”转移。建议读者在学习本节后,进一步阅读 Roslyn 源码与 .NET 运行时实现,以获得对语言设计的更深理解。

“Premature optimization is the root of all evil.” — Donald Knuth

但在系统级编程领域,正确的抽象(如 Span<T>)本身就是最优化的起点。


本文档最后更新:2026-06-14 作者:fanquanpp 版本:v2.0(金标准升级版)

返回入门指南