一、多播委托核心概念(必背)
普通单播委托:一个委托变量只能绑定一个方法,赋值使用 =,会覆盖之前的方法。
多播委托(MulticastDelegate):
通过 += 绑定多个方法、-= 移除指定方法。
调用委托时,会按绑定顺序依次执行所有绑定的方法。
所有自定义无返回值委托、Action、Func 本质都是多播委托。
二、代码基础结构
1. 定义委托
public delegate void MyDel(string s);
匹配:无返回值、单个 string 参数的方法。
2. 待绑定方法
//静态方法
static void F1(string s)
{
Console.WriteLine("这是F1的方法传递过来的参数是"+s);
}
//实例方法
public void F2(string s)
{
Console.WriteLine("这是F2的方法传递过来的参数是" + s);
}
//静态方法
public static void F3(string s)
{
Console.WriteLine("这是F3的方法传递过来的参数是" + s);
}
三、委托三种调用方式(复习)
m8("aaa"); //简写调用
m8.Invoke("bbb"); //原生调用
m8?.Invoke("ccc"); //空安全调用(推荐)
四、多播委托核心语法
1. += 叠加绑定方法
MyDel m1 = new MyDel(F1); //绑定F1
m1 += new MyDel(new Test().F2);//追加绑定F2
m1("周星驰");
执行结果:先执行 F1,再执行 F2。
2. -= 移除绑定方法
MyDel m2 = F1;
m2 += new Test().F2;
m2 -= F1; //移除F1
m2("元华"); //只剩F2执行
规律:移除谁,下次调用就不再执行谁。
五、本节课最大重难点:实例方法移除失败坑(必考)
错误案例:移除无效
MyDel m3 = F1;
m3 += new Test().F2;
m3 -= new Test().F2;
m3("梅艳芳");
错误原因(核心底层)
1. new Test().F2 每写一次,就是全新的对象。
2. 委托绑定实例方法时,记录的是:对象地址 + 方法。
3. 两次 new 出来的对象不是同一个,所以-= 找不到之前绑定的对象,移除失效。
结果:F2 依旧执行,没有被移除。
正确方案1:实例方法——复用同一个对象
MyDel m4 = F1;
Test t1 = new Test(); //唯一对象
m4 += t1.F2;
m4 -= t1.F2; //同一个对象,可以成功移除
m4("张国荣");
正确方案2:静态方法——直接移除(无对象问题)
静态方法属于类,不属于对象,不存在对象不一致问题,+=、-= 永远有效。
MyDel m5 = F1;
m5 += Test.F3;
m5 -= Test.F3; //直接成功移除
m5("周润发");
六、多播委托核心总结(满分简答题)
1. 多播委托作用
一个委托变量可以保存多个方法,调用时批量执行所有绑定方法,实现批量回调。
2. 符号区别
-
=赋值:覆盖原有所有方法,单播 -
+=追加:绑定多个方法,多播 -
-=移除:移除指定已绑定方法
3. 实例方法移除失败原因
绑定和移除时 new 了不同对象,委托内部匹配不到目标实例,导致移除无效。
4. 解决办法
实例方法:提前声明对象变量,统一复用;静态方法:直接绑定移除,无坑。
七、终极避坑口诀
单等覆盖多等加,减号移除对应法
实例绑定记对象,new两次白忙活
静态方法无对象,加减永远不会炸
三大内置委托 + Lambda表达式(Action / Func / Predicate)
一、前置知识点:全局变量 & 局部变量 & 就近原则
1. 变量作用域规则
-
局部变量:定义在方法体内,生命周期随方法结束销毁,仅当前方法可用。
-
全局变量(字段/属性):定义在 class 内部、方法外部,整个类中所有方法都可使用。
-
就近原则:当局部变量和全局变量重名时,优先使用局部变量。
2. 代码演示
public static int b = 0; //全局字段
int c { get; set; } = 0;//全局属性
static void Main(string[] args)
{
int a = 10; //局部变量
int b = 10; //局部变量(和全局b重名)
Console.WriteLine(b); //就近原则:输出局部变量10
}
static void F1()
{
Console.WriteLine(b); //无局部变量,使用全局b
}
二、C# 三大系统内置委托(必考核心)
无需手动 delegate 定义,系统自带,开发100%常用
|
内置委托 |
返回值 |
作用说明 |
泛型规则 |
|---|---|---|---|
|
Action |
无返回值 void |
用来承载任意【无返回值方法】 |
泛型全部是参数类型 |
|
Func |
有返回值 |
用来承载任意【有返回值方法】 |
最后一个是返回值,前面全是参数 |
|
Predicate |
固定 bool |
专门用来做【条件判断】 |
只有一个参数,返回值固定bool |
三、逐方法解析内置委托用法
1. Action 无返回值委托
方法定义
//接收一个 int参数、无返回值的方法
static void F1(Action<int> action)
{
action?.Invoke(10); //空安全调用,传参10
Console.WriteLine("F1的方法");
}
调用方式(匿名方法)
F1(d1);
//匿名方法
void d1(int a1)
{
Console.WriteLine("a1的值"+a1);
}
执行逻辑:F1 内部回调传入的方法,打印参数 a1=10。
2. Func 带返回值委托
方法定义
//接收int参数,返回string
static void F2(Func<int ,string> func)
{
Console.WriteLine(func?.Invoke(10));
Console.WriteLine("F2的方法");
}
完整调用演化
//1.完整写法
Func<int, string> d2 = a1 => { return "ssss"; };
F2(d2);
//2.极简写法(直接Lambda传参)
F2(v => "ssss");
规则:Func<int,string> = 参数int、返回值string。
3. Predicate 条件判断委托
方法定义
//接收int参数,固定返回bool
static void F3(Predicate<int> pre)
{
Console.WriteLine(pre(10));
Console.WriteLine("F3的方法");
}
Lambda最终极简写法(工作最常用)
F3(v => true);
//永远返回true,用于条件判断
Predicate 专门用于:判断、筛选、匹配(数组高阶函数全部靠它)。
四、Lambda表达式演化全过程(必懂)
本质:Lambda 就是匿名方法的简写,专门用来简化委托传参
演化顺序:命名方法 => 匿名方法 => Lambda完整版 => Lambda极简版
//1.命名方法(最繁琐)
void d1(int a1)
{
Console.WriteLine("a1的值"+a1);
}
F1(d1);
//2.Lambda 完整版
F1( (v) => { Console.WriteLine("v的值" + v); } );
//3.Lambda 极简版(单参数可省略括号、单返回可省略return和大括号)
F3(v => true);
F2(v => "ssss");
五、三大内置委托语法公式(背诵)
1. Action公式
Action<参数类型1,参数类型2...> :无返回值
2. Func公式
Func<参数1,参数2...,返回值类型> :最后一个泛型永远是返回值
3. Predicate公式
Predicate<参数类型> :固定返回 bool,用于条件筛选
六、核心简答题满分答案
1. 为什么要用内置委托?
不用重复手写 delegate,系统自带通用委托,适配所有回调场景,配合Lambda极大简化代码。
2. Func和Action区别?
Action 无返回值;Func 有返回值,最后一个泛型为返回值类型。
3. Predicate作用?
专门用于条件判断,接收参数、固定返回布尔值,常用于筛选、判断、查找。
七、终极背诵口诀
Action无回执行走,Func有回末尾守
Predicate判对错,返回布尔不用瞅
Lambda替匿名,回调代码最精简
局部就近优先用,全局类中全部通
委托实战案例:自定义 Array.Find 底层原理
一、案例整体功能说明
本案例复刻系统 Array.Find 查找方法 的底层原理:
1. 遍历数组,通过委托回调方法判断元素是否满足条件
2. 返回第一个满足条件的元素
3. 提供两种实现方式:系统内置 Func 委托、自定义 delegate 委托
4. 无满足条件元素,统一返回 -1
二、测试代码 & 运行结果
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//系统自带Find:找第一个偶数
Console.WriteLine(Array.Find(arr, v => v % 2 == 0)); //输出 2
//自己封装Func版:找第一个3的倍数
Console.WriteLine(MyArray.MyFind1(arr, v => v % 3 == 0)); //输出 3
//自己封装自定义委托版:找第一个3的倍数
Console.WriteLine(MyArray.MyFind2(arr, v => v % 3 == 0)); //输出 3
三、方式一:使用系统内置 Func 委托实现(MyFind1)
1. 方法签名解析
public static int MyFind1(int[] arr,Func<int,bool> f1)
Func<int, bool> 含义:
-
参数类型:int(数组元素)
-
返回值类型:bool(判断条件结果)
作用:接收一个条件判断方法,用来判断当前元素是否符合规则。
2. 核心逻辑解析
for (int i = 0; i < arr.Length; i++)
{
//回调外部传入的判断方法
if (f1(arr[i]))
{
return arr[i]; //满足条件直接返回当前元素(第一个)
}
}
return -1; //遍历完无匹配返回-1
核心思想:回调机制
方法内部只负责遍历逻辑,判断逻辑交给外部传进来的委托方法,实现逻辑解耦。
四、方式二:使用自定义委托实现(MyFind2)
1. 第一步:自定义委托
//自定义委托:匹配【int参数、bool返回值】的判断方法
public delegate bool CallBack(int a);
完全等价于 Func<int,bool>,自己手写委托类型。
2. 第二步:将委托作为参数
public static int MyFind2(int[] arr, CallBack f1)
f1 就是接收外部传入的条件判断方法。
3. 执行逻辑(和Func版本完全一致)
if (f1(arr[i]))
{
return arr[i];
}
五、两种实现方式对比(必考)
|
实现方式 |
优点 |
缺点 |
场景 |
|---|---|---|---|
|
自定义 delegate |
语义清晰、完全可控、适配特殊场景 |
需要多写一行委托定义 |
专属回调、特殊签名 |
|
系统 Func |
无需定义、代码简洁、开发常用 |
通用性强,无专属语义 |
日常回调、条件判断 |
六、Lambda 匹配原理
v => v % 3 == 0
自动匹配:
-
Func<int,bool> -
自定义
CallBack(int a)
因为:参数int、返回值bool,签名完全一致。
七、核心知识点总结(简答题满分)
1. 委托做回调的好处?
遍历逻辑固定,判断逻辑可通过委托动态传递,代码解耦、复用性极强,一个方法适配所有筛选条件。
2. Array.Find 底层原理?
遍历数组,通过传入的布尔委托方法逐个判断元素,返回第一个匹配元素,无匹配返回默认值。
3. 自定义委托和Func的关系?
功能完全一致,Func是系统预制好的通用委托,自定义委托是手动定义的专属委托。
八、终极背诵口诀
数组遍历逻辑定,判断条件委托传
Func简洁不用写,自定义委托更直观
Lambda匹配签名,回调实现万能筛选
————————泛型委托———————————
一、泛型委托核心概念(必背)
1. 普通委托弊端
普通自定义委托、内置委托类型固定,只能适配单一参数类型,想要适配 int、string、自定义类等多种类型,需要重复定义多个委托,代码冗余、复用性差。
2. 泛型委托优势
在委托名称后添加 <T>,定义泛型委托,可以适配任意数据类型,一套委托适配所有类型,彻底解决代码冗余问题。
3. 核心语法
泛型委托定义:委托名<T> + 泛型参数
二、代码核心结构解析
1. 自定义泛型委托定义
//定义泛型委托:接收任意类型T参数,返回bool
public delegate bool CallBack<T>(T v);
解析:
-
<T>:让委托支持任意数据类型 -
参数 T v:参数类型随调用时指定的类型变化
-
固定返回 bool:专门用于条件判断、筛选数据
2. 自定义泛型方法 + 自定义泛型委托(Find)
//泛型方法 + 自定义泛型委托
public static T Find<T>(T[] arr,CallBack<T> c)
{
for (int i = 0; i < arr.Length; i++)
{
//执行委托回调的条件判断方法
if (c(arr[i]))
{
return arr[i]; //返回第一个满足条件的元素
}
}
return default(T); //无匹配返回当前类型默认值
}
核心亮点
1. 数组类型 T[] 任意类型(int数组、string数组、自定义类数组)
2. 委托 CallBack<T> 跟随数组类型同步变化
3. 返回值 T 和数组类型保持一致,完全通用
4. default(T):自动返回对应类型默认值(int=0、string=null、引用类型=null)
3. 泛型方法 + 系统内置泛型委托(Find1)
public static T Find1<T>(T[] arr, Predicate<T> c)
{
for (int i = 0; i < arr.Length; i++)
{
if (c(arr[i]))
{
return arr[i];
}
}
return default(T);
}
重点知识
Predicate<T> 系统内置泛型委托
签名:delegate bool Predicate<T>(T obj)
和我们自定义的 CallBack<T>完全一模一样
专门用于:数组筛选、条件判断、查找匹配
三、测试调用代码解析
//1. int数组筛选:找第一个偶数
Console.WriteLine(Find<int>(new int[] { 1, 2, 3 }, v => v % 2 == 0));
//2. string数组筛选:找第一个以a开头的字符串
Console.WriteLine(Find<string>(new string[] { "aa", "bb", "cc" }, v => v.StartsWith("a")));
//3. Predicate版本:找第一个以b开头的字符串
Console.WriteLine(Find1<string>(new string[] { "aa", "bb", "cc" }, v => v.StartsWith("b")));
运行结果
2
aa
bb
四、两大委托对等关系(必考)
自定义泛型委托:CallBack<T>(T v)
系统内置泛型委托:Predicate<T>(T v)
二者功能、签名、用法完全一致
区别:Predicate 是系统官方封装好的泛型判断委托,开发直接用;CallBack 是手动自定义,方便理解底层原理。
五、普通委托 VS 泛型委托
|
类型 |
特点 |
复用性 |
|---|---|---|
|
普通委托 |
类型写死,只能适配一种参数 |
差,需要重复定义 |
|
泛型委托 |
T占位,任意类型适配 |
极强,一套通用所有类型 |
六、满分简答题总结
1. 什么是泛型委托?
带有泛型参数 <T> 的委托,不固定参数类型,可以适配任意数据类型,实现委托通用。
2. Predicate<T> 作用?
系统内置泛型委托,参数为任意类型T,固定返回bool,专门用于条件筛选、数据查找。
3. default(T) 作用?
返回泛型类型的默认值,解决泛型无匹配数据时的返回值问题,保证代码通用合法。
七、终极背诵口诀
委托加T变通用,任意类型都能冲
Predicate判布尔,和我自定义一模一样
泛型方法配泛托,数组筛选万能通
找不到值别慌张,default兜底最稳当
231

被折叠的 条评论
为什么被折叠?



