C# Delegate.Target/ Method
2026/7/21 9:22:58 网站建设 项目流程

<>c 详解(配合前面 Delegate.Target/Method、Lambda 闭包)

<>c= Roslyn (csc 编译器) 自动生成的内部密封类,源码不存在,反编译 ILSpy 可见,专门承载 Lambda / 匿名方法。 命名规则:<>c前缀是编译器约定符号(C# 标识符不允许<>,避免和用户类重名)。

一、两种形态:<>c / <>c__DisplayClassX_X

1. 最简:<>c无捕获局部变量的 Lambda → 静态委托缓存

Lambda不抓任何外部局部变量,编译器生成静态内部类 <>c,把 lambda 编译成静态方法,委托复用同一个静态实例(优化:避免反复 new 委托)Microsoft Developer Blogs。

// 无捕获变量 Action act = () => Console.WriteLine("test");

编译后:

[CompilerGenerated] private sealed class <>c { // 缓存委托实例,全局单例 public static readonly <>c <>9 = new <>c(); public Action <>9__0_0; // Lambda被编译成静态方法:<>9__0_0 internal void <Main>b__0_0() { Console.WriteLine("test"); } } // 委托直接绑定静态方法,Target=null(静态方法) act = <>c.<>9.<>9__0_0 ?? (<>c.<>9.<>9__0_0 = new Action(<>c.<>9.<Main>b__0_0));

👉 此时委托:Target = nullMethod = <Main>b__0_0(对应你之前看的 Delegate 两个属性)。

2. 带捕获:<>c__DisplayClass0_0闭包,抓取外部局部变量

Lambda 用到方法内局部变量,编译器生成闭包类 DisplayClass

  1. 被捕获的局部变量 → 变成这个类的public 实例字段(栈变量升级堆字段,避免方法出栈被销毁)
  2. Lambda 代码 → 编译成此类的实例方法<Main>b__0
  3. new 一个 DisplayClass 对象,委托绑定实例方法 →委托.Target = DisplayClass 实例(委托强引用这个对象,闭包内存泄漏根源)
int num = 10; Action act = () => Console.WriteLine(num);

编译等价手写:

[CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public int num; // 捕获变量升为字段 internal void <Main>b__0_0() { Console.WriteLine(num); } } // 创建闭包对象 var cls = new <>c__DisplayClass0_0(); cls.num = num; // 委托绑定实例方法 → Target=cls,Method=b__0_0 Action act = new Action(cls.<Main>b__0_0);

二、命名分段释义

<>c__DisplayClass0_0

  • <>:编译器专属标识,C# 语法不能自定义以此开头的类名,防冲突
  • c:class 缩写,代表 Lambda / 匿名方法生成类
  • DisplayClass:存储捕获变量的闭包展示类
  • 0_0:外层方法序号_当前 lambda 序号

补充其他编译器生成命名:

  • <Main>d__1async/await异步状态机类
  • <XXX>b__0:lambda 对应的编译后方法名

三、和 Delegate.Target/ Method 联动(重点,衔接上文)

表格

Lambda 场景生成类Delegate.TargetDelegate.Method
无捕获局部变量<>c(静态类)null<>c.<Main>b__xxx
捕获局部变量<>c__DisplayClassDisplayClass 实例对象DisplayClass.<Main>b__xxx

关键:只要 Target 不为 null,委托就强引用 DisplayClass 实例,实例无法 GC → 闭包内存泄漏,也就是你之前问WeakReference的使用场景:用弱引用包裹 Target,打破强引用链。

四、拓展:顶级语句 Top-level Program

C#9 顶级语句,整个代码包在自动生成的Program类内部,里面的 lambda 同样生成<>c/DisplayClass

五、快速区分记忆

  1. 不用外部变量 → <>c(静态、缓存委托、Target=null)
  2. 用外部局部变量 → <>c__DisplayClass(实例类、存字段、Target = 实例对象)

你看到的 ``1` 到底是什么?(超级简单版)

你在看泛型类型反编译 / 反射时,一定会看到类似:Action1Func2Dictionary2`

这个``1`CLR 运行时的泛型编号语法,不是 C# 语法!


1. 一句话解释

``n` = 这个泛型类型有 n 个泛型参数

  • ``1` → 1 个泛型参数
  • ``2` → 2 个泛型参数
  • ``3` → 3 个泛型参数

2. 对应关系(一看就懂)

表格

C# 写法CLR 内部名称含义
Action<T>Action1`1 个泛型参数
Action<T1,T2>Action2`2 个泛型参数
Func<TResult>Func1`1 个泛型参数
Func<T1,TResult>Func2`2 个泛型参数
List<T>List1`1 个泛型参数
Dictionary<TKey,TValue>Dictionary2`2 个泛型参数

3. 结合你刚才问的内容

你之前问:

typeof(Action<>)

它的FullName就是:

System.Action`1

意思:Action`1 = 有 1 个泛型参数的泛型委托模板


4. 为什么要用 `1 这种奇怪符号?

因为 CLR 要区分:

  • Action(无参)
  • Action<T>(1 参)
  • Action<T1,T2>(2 参)

它们名字都叫Action,所以 CLR 用1`、2`来区分。


5. 超级总结(3 秒记住)

  • ``1` = 1 个泛型参数
  • ``2` = 2 个泛型参数
  • 它是CLR 内部名称,不是 C# 代码
  • typeof(Action<>)→ 显示Action1`

如果你愿意,我可以把你前面问的:WeakReference + Delegate + Target/Method + <>c + `1

C# WeakReference 详解

WeakReference(弱引用)是 C# 中允许垃圾回收器 (GC) 回收对象,同时又能临时访问该对象的特殊引用类型。

它和我们平时用的强引用(直接赋值var obj = new MyClass())核心区别:

  • 强引用:只要引用存在,GC永远不会回收对象
  • 弱引用:GC 可以随时回收对象,你只是保留了一个 "访问权限"

一、核心概念

  1. 弱引用不阻止垃圾回收即使你持有WeakReference,GC 判定对象无强引用时,会直接回收该对象内存。
  2. 使用前必须检查访问弱引用对象前,必须判断对象是否还存活(未被回收)。
  3. 适用场景缓存、非关键数据、大对象临时持有(不希望占用内存不释放)。

二、基础用法

1. 创建弱引用

// 1. 创建一个强引用对象 var myObj = new MyClass { Name = "测试对象" }; // 2. 创建弱引用(指向 myObj) WeakReference weakRef = new WeakReference(myObj); // 3. 释放强引用(让 GC 可以回收对象) myObj = null;

2. 安全获取弱引用对象(必须判空)

// 正确写法:先获取,再判断是否为 null if (weakRef.Target is MyClass obj && obj != null) { // 对象存活,可以使用 Console.WriteLine(obj.Name); } else { // 对象已被GC回收 Console.WriteLine("对象已被销毁"); }

3. 手动触发 GC 测试回收效果

using System; class MyClass { public string Name { get; set; } } class Program { static void Main() { var myObj = new MyClass { Name = "弱引用测试" }; WeakReference weakRef = new WeakReference(myObj); // 释放强引用 myObj = null; // 强制GC回收 GC.Collect(); GC.WaitForPendingFinalizers(); // 检查对象 if (weakRef.Target is MyClass obj) Console.WriteLine("对象存活:" + obj.Name); else Console.WriteLine("对象已被GC回收"); } }

输出对象已被GC回收

三、重要属性 & 方法

表格

成员作用
Target获取 / 设置弱引用指向的对象(使用前必须判空)
IsAlive判断对象是否未被回收(不推荐直接用

⚠️ 不推荐直接用IsAlive

// 错误写法 if (weakRef.IsAlive) { // 这里可能已经被GC回收了!存在竞态条件 var obj = (MyClass)weakRef.Target; }

原因:判断IsAlive后、访问Target前,GC 可能刚好回收对象。

正确做法:直接获取Target并判空。

四、进阶:WeakReference<T> 泛型版本

C# 4.0 引入了泛型弱引用,类型安全、无需强制转换,推荐优先使用。

// 泛型弱引用(推荐) WeakReference<MyClass> weakRef = new WeakReference<MyClass>(myObj); // 安全获取 if (weakRef.TryGetTarget(out MyClass obj)) { // 使用 obj }

TryGetTarget是原子操作,线程安全,无竞态问题。

五、典型使用场景

  1. 内存敏感型缓存缓存图片、数据模型,内存不足时让 GC 自动清理,不影响程序稳定性。
  2. 事件订阅避免内存泄漏弱引用事件,防止长生命周期对象持有短生命周期对象导致无法回收。
  3. 大对象临时缓存不希望大对象常驻内存,又想在未回收时复用。

六、注意事项

  1. 不要用弱引用持有短时间、高频使用的对象,性能不如强引用。
  2. 值类型不能用弱引用(值类型在栈上,GC 不管理)。
  3. 弱引用对象不能依赖它存活,随时可能为 null。
  4. 优先使用WeakReference<T>,而非非泛型WeakReference

总结

  1. WeakReference=不阻止 GC 回收的引用
  2. 核心用法:获取 Target 判空 / TryGetTarget
  3. 优先用WeakReference<T>(类型安全、线程安全)
  4. 适用:缓存、非关键数据、大对象临时持有
  5. 禁忌:依赖弱引用对象必须存在、高频小对象使用

C# Delegate(委托)详解

Delegate(委托)是 C# 中的类型安全的函数指针,本质是一个存储方法引用的类。 简单说:委托就是 “可以把方法当成变量 / 参数 / 返回值来传递” 的工具


一、核心概念

  1. 委托是一个类,用来封装方法(静态 / 实例方法都可以)
  2. 委托定义了方法的签名(返回值 + 参数),符合签名的方法都能被它引用
  3. 用途:回调、事件、Lambda、匿名方法、多播全靠委托实现

二、最基础用法(4 步)

1. 声明委托类型

// 定义:无参数、无返回值 public delegate void MyDelegate(); // 定义:带 int 参数、返回 string public delegate string CalcDelegate(int a, int b);

2. 写一个匹配签名的方法

void SayHi() { Console.WriteLine("你好,委托!"); } string Add(int x, int y) { return (x + y).ToString(); }

3. 创建委托实例,绑定方法

MyDelegate del = SayHi; // 绑定方法 CalcDelegate calc = Add;

4. 调用委托(等于调用方法)

del(); // 输出:你好,委托! string res = calc(10, 20); // 30

三、高级用法:委托作为方法参数(回调)

这是委托最常用的场景:把方法传给另一个方法执行。

// 接收委托作为参数 void Execute(MyDelegate action) { Console.WriteLine("执行前..."); action(); // 调用传入的方法 Console.WriteLine("执行后..."); } // 使用 Execute(SayHi);

输出:

执行前... 你好,委托! 执行后...

四、多播委托(一个委托调用多个方法)

委托可以用+=绑定多个方法,一次性调用全部。

MyDelegate multiDel = Method1; multiDel += Method2; // 追加方法 multiDel(); // 一次调用,执行两个方法

五、系统内置委托(99% 场景不用自己定义)

C# 提供了 3 个万能委托,开发中直接用,不用手写 delegate

1.Action(无返回值)

  • Action:无参无返回
  • Action<T>:1 个参数无返回
  • Action<T1,T2>:2 个参数无返回
Action<string> showMsg = Console.WriteLine; showMsg("测试");

2.Func(有返回值)

最后一个泛型是返回值

Func<int, int, int> add = (a, b) => a + b; int sum = add(2, 3); // 5

3.Predicate(返回 bool,判断用)

Predicate<int> isPositive = n => n > 0; bool res = isPositive(10); // true

六、委托 + Lambda(最常用写法)

实际开发中,几乎不单独写方法,直接用 Lambda:

// 一行完成:定义 + 绑定 + 调用 Func<int, int> square = x => x * x; int result = square(5); // 25

七、委托 vs 接口(简单区别)

  • 委托:专注方法传递、回调、事件
  • 接口:专注一组功能规范
  • 简单记:只传一个方法用委托,一组方法用接口

八、关键要点总结

  1. 委托 = 类型安全的函数指针
  2. 作用:把方法当参数传递(回调核心)
  3. 优先用系统内置:Action/Func
  4. 支持多播(一个委托调多个方法)
  5. 事件(event)的基础

一句话记住委托

委托就是:让方法可以像变量一样被传来传去。

彻底搞懂 Delegate 的MethodTarget属性

你贴的这两个属性,是委托最核心的两个信息,我用最简单、最直白的方式给你讲透,一看就懂。


一句话总结

  • Method:委托指向哪个方法(方法的信息:名字、参数、私有 / 公有)
  • Target:这个方法属于哪个对象(实例对象 /null 静态方法)

1.Target属性:委托属于哪个对象?

规则:

  • 实例方法Target=调用该方法的对象本身
  • 静态方法Target=null

示例

class MyClass { // 实例方法 public void Show() { } // 静态方法 public static void StaticShow() { } }
MyClass obj = new MyClass(); // 委托绑定 实例方法 Action action1 = obj.Show; Console.WriteLine(action1.Target); // 输出:MyClass 对象(不为null) // 委托绑定 静态方法 Action action2 = MyClass.StaticShow; Console.WriteLine(action2.Target); // 输出:null

2.Method属性:委托指向哪个方法?

返回一个MethodInfo,包含方法的所有信息

  • 方法名
  • 返回值
  • 参数列表
  • 访问权限(public/private)
  • 是否静态

示例

Action action = obj.Show; MethodInfo method = action.Method; Console.WriteLine(method.Name); // Show Console.WriteLine(method.IsStatic); // False Console.WriteLine(method.IsPublic); // True

3. 最直观对比表

表格

委托绑定的方法TargetMethod
实例方法方法所属的对象方法信息
静态方法null方法信息

4. 为什么这两个属性很重要?

用途 1:判断两个委托是否指向同一个方法

if (del1.Target == del2.Target && del1.Method == del2.Method) { // 两个委托完全一样 }

用途 2:反射动态调用

你可以通过Target+Method手动调用方法:

action.Method.Invoke(action.Target, null);

用途 3:事件 / 委托调试、日志记录

记录当前委托执行的是哪个对象的哪个方法


5. 结合你之前问的 WeakReference<Target>

你刚才问过WeakReference,这里有个超级关键的知识点

✅ 委托的 Target 就是强引用

MyClass obj = new MyClass(); Action action = obj.Show;

只要action不释放,obj 永远不会被 GC 回收

这就是委托导致内存泄漏的根本原因。


总结(超级好记)

  • Target= 方法属于哪个对象(实例方法有值,静态方法为 null)
  • Method= 方法本身的信息(叫什么、参数、权限)

typeof(Action<T>)到底是什么?

我用最直白、最核心的方式给你讲清楚,结合你刚才看的Delegate.Target/Method,一次性打通!


1. 先看本质

typeof(Action<T>)

它 = 泛型委托Action<T>的「类型对象」(Type 实例)不是对象,不是方法,是「类型本身」的元数据


2. 和普通ActionAction<int>的区别

表格

写法含义
typeof(Action)非泛型、无参委托类型
typeof(Action<int>)构造好的泛型委托(int 版)
typeof(Action<T>)未绑定的泛型定义(开放泛型)

重点:

  • Action<T>是开放泛型(open generic)
  • Action<int>是封闭泛型(closed generic)

你写typeof(Action<T>),得到的是:

System.Action`1[T]

`1 表示这是有 1 个泛型参数的泛型类型定义。


3. 代码一看就懂

// 泛型定义(开放泛型) Type openType = typeof(Action<>); // 注意:Action<> 等价 Action<T> Console.WriteLine(openType); // 输出:System.Action`1[T] // 封闭泛型(已指定类型) Type closedType = typeof(Action<int>); Console.WriteLine(closedType); // 输出:System.Action`1[System.Int32]

4. 它和你刚才看的 Target/Method 有什么关系?

关系非常大!

委托本质结构:

Delegate ├─ Target(对象) ├─ Method(方法) └─ MethodPtr(函数指针)

typeof(Action<T>)是:这个委托属于哪个「类型」


5. 最实用的用途(你以后一定会用到)

用途 1:判断一个对象是不是 Action<T> 委托

bool isGenericAction = del.GetType().GetGenericTypeDefinition() == typeof(Action<>);

用途 2:反射创建泛型委托

Type genericType = typeof(Action<>); Type constructedType = genericType.MakeGenericType(typeof(string)); // 动态创建 Action<string> Delegate action = Delegate.CreateDelegate(constructedType, ...);

用途 3:获取泛型参数类型

Type[] args = typeof(Action<int>).GetGenericArguments(); Type t = args[0]; // int

6. 超级总结(3 秒记住)

  • typeof(Action<T>)= 泛型委托的「原型 / 模板」
  • 它是开放泛型类型定义
  • 必须MakeGenericType后才能真正使用
  • Target/Method关系:Target是实例,typeof是类型

一句话终极记忆

typeof(Action<>)是委托的「模具」,Action<int>是用模具造出来的「成品」。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询