<>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。
cs
// 无捕获变量
Action act = () => Console.WriteLine("test");
编译后:
cs
[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 = null、Method = <Main>b__0_0(对应你之前看的 Delegate 两个属性)。
2. 带捕获:<>c__DisplayClass0_0(闭包,抓取外部局部变量)
Lambda 用到方法内局部变量,编译器生成闭包类 DisplayClass:
- 被捕获的局部变量 → 变成这个类的public 实例字段(栈变量升级堆字段,避免方法出栈被销毁)
- Lambda 代码 → 编译成此类的实例方法
<Main>b__0 - new 一个 DisplayClass 对象,委托绑定实例方法 → 委托.Target = DisplayClass 实例(委托强引用这个对象,闭包内存泄漏根源)
cs
int num = 10;
Action act = () => Console.WriteLine(num);
编译等价手写:
cs
[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__1:async/await异步状态机类<XXX>b__0:lambda 对应的编译后方法名
三、和 Delegate.Target/ Method 联动(重点,衔接上文)
表格
| Lambda 场景 | 生成类 | Delegate.Target | Delegate.Method |
|---|---|---|---|
| 无捕获局部变量 | <>c(静态类) | null | <>c.<Main>b__xxx |
| 捕获局部变量 | <>c__DisplayClass | DisplayClass 实例对象 | DisplayClass.<Main>b__xxx |
关键:只要 Target 不为 null,委托就强引用 DisplayClass 实例,实例无法 GC → 闭包内存泄漏 ,也就是你之前问
WeakReference的使用场景:用弱引用包裹 Target,打破强引用链。
四、拓展:顶级语句 Top-level Program
C#9 顶级语句,整个代码包在自动生成的Program类内部,里面的 lambda 同样生成<>c/DisplayClass。
五、快速区分记忆
- 不用外部变量 → <>c(静态、缓存委托、Target=null)
- 用外部局部变量 → <>c__DisplayClass(实例类、存字段、Target = 实例对象)
你看到的 ``1` 到底是什么?(超级简单版)
你在看 泛型类型 反编译 / 反射时,一定会看到类似: Action1、Func2、Dictionary2`
这个 ``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 就是:
cs
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 可以随时回收对象,你只是保留了一个 "访问权限"
一、核心概念
- 弱引用不阻止垃圾回收 即使你持有
WeakReference,GC 判定对象无强引用时,会直接回收该对象内存。 - 使用前必须检查 访问弱引用对象前,必须判断对象是否还存活(未被回收)。
- 适用场景 缓存、非关键数据、大对象临时持有(不希望占用内存不释放)。
二、基础用法
1. 创建弱引用
cs
// 1. 创建一个强引用对象
var myObj = new MyClass { Name = "测试对象" };
// 2. 创建弱引用(指向 myObj)
WeakReference weakRef = new WeakReference(myObj);
// 3. 释放强引用(让 GC 可以回收对象)
myObj = null;
2. 安全获取弱引用对象(必须判空)
cs
// 正确写法:先获取,再判断是否为 null
if (weakRef.Target is MyClass obj && obj != null)
{
// 对象存活,可以使用
Console.WriteLine(obj.Name);
}
else
{
// 对象已被GC回收
Console.WriteLine("对象已被销毁");
}
3. 手动触发 GC 测试回收效果
cs
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
cs
// 错误写法
if (weakRef.IsAlive)
{
// 这里可能已经被GC回收了!存在竞态条件
var obj = (MyClass)weakRef.Target;
}
原因 :判断 IsAlive 后、访问 Target 前,GC 可能刚好回收对象。
正确做法 :直接获取 Target 并判空。
四、进阶:WeakReference<T> 泛型版本
C# 4.0 引入了泛型弱引用,类型安全、无需强制转换,推荐优先使用。
cs
// 泛型弱引用(推荐)
WeakReference<MyClass> weakRef = new WeakReference<MyClass>(myObj);
// 安全获取
if (weakRef.TryGetTarget(out MyClass obj))
{
// 使用 obj
}
TryGetTarget 是原子操作,线程安全,无竞态问题。
五、典型使用场景
- 内存敏感型缓存 缓存图片、数据模型,内存不足时让 GC 自动清理,不影响程序稳定性。
- 事件订阅避免内存泄漏 弱引用事件,防止长生命周期对象持有短生命周期对象导致无法回收。
- 大对象临时缓存 不希望大对象常驻内存,又想在未回收时复用。
六、注意事项
- 不要用弱引用持有短时间、高频使用的对象,性能不如强引用。
- 值类型不能用弱引用(值类型在栈上,GC 不管理)。
- 弱引用对象不能依赖它存活,随时可能为 null。
- 优先使用
WeakReference<T>,而非非泛型WeakReference。
总结
WeakReference= 不阻止 GC 回收的引用- 核心用法:获取 Target 判空 / TryGetTarget
- 优先用
WeakReference<T>(类型安全、线程安全) - 适用:缓存、非关键数据、大对象临时持有
- 禁忌:依赖弱引用对象必须存在、高频小对象使用
C# Delegate(委托)详解
Delegate(委托) 是 C# 中的类型安全的函数指针 ,本质是一个存储方法引用 的类。 简单说:委托就是 "可以把方法当成变量 / 参数 / 返回值来传递" 的工具。
一、核心概念
- 委托是一个类 ,用来封装方法(静态 / 实例方法都可以)
- 委托定义了方法的签名(返回值 + 参数),符合签名的方法都能被它引用
- 用途:回调、事件、Lambda、匿名方法、多播 全靠委托实现
二、最基础用法(4 步)
1. 声明委托类型
cs
// 定义:无参数、无返回值
public delegate void MyDelegate();
// 定义:带 int 参数、返回 string
public delegate string CalcDelegate(int a, int b);
2. 写一个匹配签名的方法
cs
void SayHi()
{
Console.WriteLine("你好,委托!");
}
string Add(int x, int y)
{
return (x + y).ToString();
}
3. 创建委托实例,绑定方法
cs
MyDelegate del = SayHi; // 绑定方法
CalcDelegate calc = Add;
4. 调用委托(等于调用方法)
cs
del(); // 输出:你好,委托!
string res = calc(10, 20); // 30
三、高级用法:委托作为方法参数(回调)
这是委托最常用的场景:把方法传给另一个方法执行。
cs
// 接收委托作为参数
void Execute(MyDelegate action)
{
Console.WriteLine("执行前...");
action(); // 调用传入的方法
Console.WriteLine("执行后...");
}
// 使用
Execute(SayHi);
输出:
执行前...
你好,委托!
执行后...
四、多播委托(一个委托调用多个方法)
委托可以用 += 绑定多个方法,一次性调用全部。
cs
MyDelegate multiDel = Method1;
multiDel += Method2; // 追加方法
multiDel(); // 一次调用,执行两个方法
五、系统内置委托(99% 场景不用自己定义)
C# 提供了 3 个万能委托,开发中直接用,不用手写 delegate:
1. Action(无返回值)
Action:无参无返回Action<T>:1 个参数无返回Action<T1,T2>:2 个参数无返回
cs
Action<string> showMsg = Console.WriteLine;
showMsg("测试");
2. Func(有返回值)
最后一个泛型是返回值
cs
Func<int, int, int> add = (a, b) => a + b;
int sum = add(2, 3); // 5
3. Predicate(返回 bool,判断用)
cs
Predicate<int> isPositive = n => n > 0;
bool res = isPositive(10); // true
六、委托 + Lambda(最常用写法)
实际开发中,几乎不单独写方法,直接用 Lambda:
cs
// 一行完成:定义 + 绑定 + 调用
Func<int, int> square = x => x * x;
int result = square(5); // 25
七、委托 vs 接口(简单区别)
- 委托 :专注方法传递、回调、事件
- 接口 :专注一组功能规范
- 简单记:只传一个方法用委托,一组方法用接口
八、关键要点总结
- 委托 = 类型安全的函数指针
- 作用:把方法当参数传递(回调核心)
- 优先用系统内置:
Action/Func - 支持多播(一个委托调多个方法)
- 是事件(event) 的基础
一句话记住委托
委托就是:让方法可以像变量一样被传来传去。
彻底搞懂 Delegate 的 Method 和 Target 属性
你贴的这两个属性,是委托最核心的两个信息,我用最简单、最直白的方式给你讲透,一看就懂。
一句话总结
Method:委托指向哪个方法(方法的信息:名字、参数、私有 / 公有)Target:这个方法属于哪个对象(实例对象 /null 静态方法)
1. Target 属性:委托属于哪个对象?
规则:
- 实例方法 →
Target= 调用该方法的对象本身 - 静态方法 →
Target= null
示例
cs
class MyClass
{
// 实例方法
public void Show() { }
// 静态方法
public static void StaticShow() { }
}
cs
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)
- 是否静态
示例
cs
Action action = obj.Show;
MethodInfo method = action.Method;
Console.WriteLine(method.Name); // Show
Console.WriteLine(method.IsStatic); // False
Console.WriteLine(method.IsPublic); // True
3. 最直观对比表
表格
| 委托绑定的方法 | Target | Method |
|---|---|---|
| 实例方法 | 方法所属的对象 | 方法信息 |
| 静态方法 | null | 方法信息 |
4. 为什么这两个属性很重要?
用途 1:判断两个委托是否指向同一个方法
cs
if (del1.Target == del2.Target && del1.Method == del2.Method)
{
// 两个委托完全一样
}
用途 2:反射动态调用
你可以通过 Target + Method 手动调用方法:
cs
action.Method.Invoke(action.Target, null);
用途 3:事件 / 委托调试、日志记录
记录当前委托执行的是哪个对象的哪个方法。
5. 结合你之前问的 WeakReference<Target>
你刚才问过 WeakReference,这里有个超级关键的知识点:
✅ 委托的 Target 就是强引用!
cs
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. 和普通 Action、Action<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. 代码一看就懂
cs
// 泛型定义(开放泛型)
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 有什么关系?
关系非常大!
委托本质结构:
cs
Delegate
├─ Target(对象)
├─ Method(方法)
└─ MethodPtr(函数指针)
而 typeof(Action<T>) 是: 这个委托属于哪个「类型」
5. 最实用的用途(你以后一定会用到)
用途 1:判断一个对象是不是 Action<T> 委托
bool isGenericAction = del.GetType().GetGenericTypeDefinition() == typeof(Action<>);
用途 2:反射创建泛型委托
cs
Type genericType = typeof(Action<>);
Type constructedType = genericType.MakeGenericType(typeof(string));
// 动态创建 Action<string>
Delegate action = Delegate.CreateDelegate(constructedType, ...);
用途 3:获取泛型参数类型
cs
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> 是用模具造出来的「成品」。