Delegate.Target/ Method

<>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 = nullMethod = <Main>b__0_0(对应你之前看的 Delegate 两个属性)。

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

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

  1. 被捕获的局部变量 → 变成这个类的public 实例字段(栈变量升级堆字段,避免方法出栈被销毁)
  2. Lambda 代码 → 编译成此类的实例方法<Main>b__0
  3. 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__1async/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

五、快速区分记忆

  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 就是:

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 可以随时回收对象,你只是保留了一个 "访问权限"

一、核心概念

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

二、基础用法

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 是原子操作,线程安全,无竞态问题。

五、典型使用场景

  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. 声明委托类型

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 接口(简单区别)

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

八、关键要点总结

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

一句话记住委托

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

彻底搞懂 Delegate 的 MethodTarget 属性

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


一句话总结

  • 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. 和普通 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. 代码一看就懂

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> 是用模具造出来的「成品」。

相关推荐
Insseals6 分钟前
因斯特浮动模块快速接头✨五大核心优势
前端
沐土Arvin22 分钟前
港澳台行政区域json
前端
程序员鱼皮43 分钟前
我花 300 块,让 Claude Fable 5 开发桌面 APP,值么?
前端
William_Xu1 小时前
JavaScript 并发控制
前端
拾年2751 小时前
从零手写 Ajax:用原生 XHR 搭建前后端交互全流程
前端·javascript·ajax
光影少年1 小时前
懒加载与分包:React.lazy + Suspense
前端·react.js·掘金·金石计划
小林ixn1 小时前
你以为你懂 + 号?看完这篇 Bun + TS 实战,才发现以前全写错了
前端·javascript·typescript
namexingyun1 小时前
开源前端生态如何成为 AI UI 生成的“燃料“:shadcn/ui、Tailwind CSS、Storybook 技术价值全解剖
java·前端·人工智能·python·ui·开源·ai编程
Zyed2 小时前
[STM32]Day15读写FLASH+读取ID
前端·stm32·性能优化
LT10157974442 小时前
2026年UI自动化测试平台选型指南:全界面自动化覆盖方案
运维·ui·自动化