C# 多播委托 +三大内置委托 + Lambda表达式(Action / Func / Predicate)+委托实战案例:自定义 Array.Find 底层原理+ 泛型委托

一、多播委托核心概念(必背)

普通单播委托:一个委托变量只能绑定一个方法,赋值使用 =,会覆盖之前的方法。

多播委托(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兜底最稳当

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值