一、委托
1.1概念
委托是一种引用类型,它可以用于封装并传递方法作为参数。委托可以理解为是一个指向方法的**“指针”,它允许将方法作为参数传递给其他方法或存储在数据结构中,然后稍后调用这些方法。(委托可以看作时函数的容器)**
1.2目的作用
委托的目的就是将方法当作参数进行传递 ;
封装变化的请求
1.3定义
- 委托:将方法作为参数进行传递 方法是有签名( 签名:返回值类型 ,参数列表(参数的个数 ,参数类型))。
- 委托需要和方法有相同的签名 也就是说委托和方法 要参数相同 返回值相同 你才可以引用这个方法。
- 访问修饰符:public protected internal…委托的访问修饰符一般都定义成public,因为我们的程序可能会涉及到跨程序集调用。
- 委托和类是平级的 不要定义在类里面
//01定义委托
public delegate void DelTest();
internal class Program
{
static void Main(string[] args)
{
//03.创建委托对象,两种方式
DelTest delTest = new DelTest(Test);
//或者 创建委托变量可以省略new 关键字,直接将要传递的方法赋值即可,注意,方法在赋值的时候,不要写();
//虽然我们没有创建委托对象(new出来的才叫对象),但是编译器在编译代码的是很好还是去帮助我们new了委托对象
DelTest delTest1 = Test;
//04.调用委托,两种方式:委托名字();或者委托名字.Invoke();
delTest();
delTest.Invoke();//调用(委托)对象里面存在的方法
// delTest1();
}
//02.定义方法
public static void Test()
{
Console.WriteLine("没有参数没有返回值");
}
}
}
1.4匿名方法创建委托
- 当使用的方法只使用一次时,没必要单独创建一个方法,直接定义委托对象时创建匿名方法
DelTest delTest = delegate () { Console.WriteLine("没有返回值没有参数"); };//后面加封号
delTest();
- 匿名方法允许你在需要委托的地方直接定义方法体,而不需要显式地创建一个单独的方法。
//没有参数 没有返回值
DelTest1 delTest1 = delegate () { Console.WriteLine("没有参数 没有返回值"); };
delTest1();
//没有参数 有返回值
DelTest2 delTest2 = delegate () { return "没有参数 有返回值"; };
Console.WriteLine(delTest2());
//有参数 没有返回值
DelTest3 delTest3 = delegate (string str) { Console.WriteLine("有参数 没有返回值"+str); };
delTest3("delTest3");
//有参数 有返回值
DelTest4 delTest4 = delegate (int n1,int n2) { return n1 + n2; };
Console.WriteLine(delTest4(10,20));
//没有参数 没有返回值
public delegate void DelTest1();
//没有参数 有返回值
public delegate string DelTest2();
//有参数 没有返回值
public delegate void DelTest3(string name);
//有参数 有返回值
public delegate int DelTest4(int n1,int n2);
1.5委托+Lambda方式简化创建(常用)
- Lambda格式
(parameters) => expression
其中,parameters是传递给委托的参数列表,可以是空的或包含多个参数;expression是一个表达式,它将计算并返回给委托调用者的值。
2. 创建委托:匿名函数+Lambda+委托
1.没有参数 没有返回值
1.1匿名方法
DelTest1 delTest1 = delegate () { Console.WriteLine("没有参数 没有返回值"); };
delTest1();
1.2lambda1
DelTest1 delTest11 = () => { Console.WriteLine("没有参数 没有返回值"); };
delTest11();
- 图中箭头指向的地方对应位置作用一样
2.有参数 没有返回值
DelTest3 delTest3 = delegate (string name) { Console.WriteLine(name); };
delTest3("有参数 没有返回值");
DelTest3 delTest31 = (name) => { Console.WriteLine(name); };
当你是一个参数的时候可以去掉() 但是你没有参数列表的时候需要加上()
DelTest3 delTest32 = name => { Console.WriteLine(name); };
delTest31("有参数 没有返回值");
1.6Action/Func创建委托
Action委托本身不包含返回值,主要封装那些没有返回结果的函数。Func委托则可用于需要返回值的情况。
//00、自己定义的泛型委托
DelTest4<int> delTest4 = n1 => { Console.WriteLine(n1); };
delTest4(230);
泛型委托
public delegate void DelTest4<T>(T t);
//01、 Action
//Action就是系统帮我们定义好的一个没有参数没有返回值的委托
//委托 没有参数没有返回值 直接使用 不需要再去定义一个委托
Action action = () => { Console.WriteLine("a"); };
//调用内置委托和调用普通委托一样
action();
//TResult 代表返回值的类型
//第一个int 表示参数一为int类型
//第二个int 表示参数二为int类型
//第三个int 表示参数三为int类型
//第四个string 表示参数四为返回值为string
//最后一个永远是返回值
Func<int, int, int,string> func = (n1, n2,n3) => { return "张三的成绩为"+n1+n2+n3; };
Console.WriteLine(func(100, 200,300));
1.7多播委托
多播委托时为了让同一个委托对象,一行调用多个方法同时执行,如游戏中,一个人物按下开火键(委托对象),手中的冲锋枪和步枪同时开火,如果不使用多播委托,需要先进行武器切换选择其中一个进行开火。
Action action = M1;
//如果使用 多播委托 必须在调用之前进行绑定
//多播委托和字符串拼接类似 每拼接一次 在实际的内存中 都会创建一个新的委托对象
//我们看到的是一个委托对象指向了多个方法
//实际上每次拼接后 创建出来新的对象结果
action += M2;
action += M3;
action += M4;
action += M5;
//从结果来看 多播委托的调用是顺序的 ,但是不代表多播委托 的调用就是顺序的 微软随时可以改变顺序
//使用队列
action();
static void M1()
{
Console.WriteLine("我是第1个方法");
}
static void M2()
{
Console.WriteLine("我是第2个方法");
}
static void M3()
{
Console.WriteLine("我是第3个方法");
}
static void M4()
{
Console.WriteLine("我是第4个方法");
}
static void M5()
{
Console.WriteLine("我是第5个方法");
}
1.8委托注意事项
委托不能为空,为空的委托不能调用,报错;
空委托不能调用,所以使用委托前要进行判断:
if(attack!=null)
{
attack();//调用委托
}
上面的方式过于繁琐,可以简化:
//无参数
attack?.Invoke();
//有参数
attack?.Invoke(a,b);
二、事件
1.概念
C#中的事件是一种特别的委托,它可以被多个方法订阅,并且在特定情况下触发这些方法的执行。
2.使用
2.1事件的定义
- 声明事件:在C#中,事件通常通过event关键字声明。它定义了一个可以由多个方法订阅的特殊类型的委托。例如,声明一个没有返回值且不带参数的事件:
public event Action MyEvent;。
- 委托类型:事件的背后是一个委托类型,它定义了订阅事件的方法的签名。例如,使用内置的Action委托或自定义委托来定义事件。
- 访问修饰符:事件的声明可以包含访问修饰符,如public或private,以控制对事件的访问级别。
2.2事件的订阅
- 订阅事件:订阅事件是通过+=运算符完成的。例如,
myObject.MyEvent += MyMethod;
,其中MyMethod是响应事件的方法。 - 触发事件:当特定条件满足时,事件会被触发。这通常是在类的内部,通过
EventName?.Invoke()
形式调用。例如,MyEvent?.Invoke();
会触发所有订阅了MyEvent的方法。 - 取消订阅:使用-=运算符可以取消事件订阅,如
myObject.MyEvent -= MyMethod;
。
3.案例
委托会遇到的问题
//问题1:我们通过委托来实现事件 由于委托在定义的时候,一般都定义成public 这样就导致了 用户端 不给委托赋值的情况了,也可以直接调用
//问题2:由于委托本质上就是一个数据类型 所以如果赋值为null的话 就会把之前的值覆盖掉 变成一个null对象
//01、直接调用没赋值的委托,可以调用,但是这是不正确的
musicPlayer.BeforePlayMusic();
musicPlayer.AfterPlayMusic();
//02、直接调用事件
//外界不允许直接调用事件 因为事件的语法规定了这一点
musicPlayer.BeforePlayMusic();
musicPlayer.AfterPlayMusic();
//通过音乐播放器 讲解事件在程序中是啥
//实例化音乐播放器类
MusicPlayer musicPlayer = new MusicPlayer();
musicPlayer.BeforePlayMusic += () => { Console.WriteLine("加载我们的歌词"); };
musicPlayer.AfterPlayMusic += () => { Console.WriteLine("跳转下一曲"); };
musicPlayer.BeforePlayMusic += null;
musicPlayer.AfterPlayMusic += null;
//用户单击了播放按钮
musicPlayer.StartMusic();
musicPlayer.EndMusic();
//通俗的说:就是发生【某个变化】的是时候 触发某段代码
//没有用到事件---- - 没有发生变化
//需求1: 我希望在播放音乐之前 ,能够加载我们的歌词
//需求2:我希望播放音乐结束后 能够自动跳转下一曲
//需求应该交给用户 :委托
class MusicPlayer
{
//01、声明两个事件
public event Action BeforePlayMusic;
public event Action AfterPlayMusic;
//播放音乐 不能给别人调用
private void MusicPlay()
{
//判断用户是否是会员
//判断用户播放的音乐是否是会员音乐
//去数据量里面加载音乐文件
//开始播放.....
//加载我们的歌词
BeforePlayMusic();
Console.WriteLine("音乐播放中");
}
//点击按钮播放音乐
public void StartMusic()
{
this.MusicPlay();
Thread.Sleep(2000);
}
public void EndMusic()
{
Console.WriteLine("音乐关闭中");
AfterPlayMusic();
//跳转下一曲
}
}
三、委托与事件区别
- 事件只能在类的内部进行触发,不能在类的外部进行触发。而委托在类的内部和外部都可触发;
- 事件是一个特殊的委托,查看反编译工具之后的代码,发现事件是一个 private 委托
委托测试:
//03、触发类内部定义的委托
Test te = new Test();
te.SayHello();
//04在类外部进行委托对象赋值并处罚
Test.Foo fo= () => { Console.WriteLine("02、类外部触发的委托!"); };
fo();
//创建类
class Test
{
//01定义委托
public delegate void Foo();
public void SayHello()
{
//02在类内部对委托对象进行赋值
Foo fo=()=> { Console.WriteLine("01、类内部触发的委托!"); };
fo();
}
}
事件测试:
//03实例化类并触发事件
Test te = new Test();
te.SayHello();
class Test
{
//01、定义事件
public event Foo Foo1;
public void SayHello()
{
//02、方法绑定
Foo1+= () => { Console.WriteLine("类内部触发的事件!"); };
Foo1();
}
}
//02类外部进行绑定方法,并触发
te.Foo1 += () => { Console.WriteLine("a2"); };
te.Foo1();
class Test
{
//01定义事件
public event Foo Foo1;
}
报错,事件无法在类的外部进行触发