委托
委托是函数(方法)的容器,类似存放(变量)的容器
1. 委托的语法
访问修饰符 delegate 返回值 委托名(参数列表);
注意1:delegate可以声明在namespace中,也可以声明在class中。
更多的是声明在namespace中。
注意2:访问修饰符可以不写,默认是public,可以在别的命名空间使用。
private不能在别的命名空间使用。
2. 委托的定义
只是定义规则,并没有使用
// 方法容器规则:只能存储带有int返回值、2个int参数的方法
public delegate int Calculate(int x, int y);
// 委托是支持 泛型的 可以让返回值和参数 可变 更方便我们的使用
public delegate T myFun<T>(T t);
3. 委托的声明和调用
public delegate int Calculate(int x, int y);
public void Test1()
{
Calculate myFun = null;
// 将方法放入容器的两种方法
myFun = new myFun(Test2);
myFun = Test2;
// 调用容器里面的方法
myFun.Invoke(3, 4);
myFun(3, 4);
}
public int Test2(int a, int b)
{
return a + b;
}
4. 委托的使用
4.1 委托作为类的成员
class Test
{
public MyFun fun;
}
4.2 委托作为函数的参数
class Test
{
public void TestFun( MyFun fun){
fun();
}
}
4.3 委托可以存储多个函数(多播委托)
存在一个主委托,内含多个子委托,运行主委托的时候,则会将蕴含的子委托依次执行。
public delegate int Calculate(int x, int y); // 创建一个委托类型
Calculate cal0 = new Calculate(Add); // 委托Add方法
Calculate cal1 = new Calculate(Multiple); // 委托Multiple方法
// 多播
// 1. 通过表达式方式创建多播委托
Calculate cal = cal0 + cal1;
// 2. 初始化为null, 通过加法运算创建委托
Calculate cal = null;
cal += cal0;
cal += cal1;
// 3. 通过等于子委托来创建多播委托
Calculate cal = cal0; // 并不是将cal0的内存给到cal,而是创建全新的cal对象
cal += cal1;
4.4 获得每一个子委托的返回值
如果采用多播委托,那这个的返回值会是最后一次添加子委托的返回值,如:Calculate cal = cal0 + cal1;, 调用int c = cal();,其中c其实是cal1的返回值
那么如何获取每一个子委托的返回值呢?
Delegate[] ds = cal.GetInvocationList(); // 获取每一个委托
for(int i = 0; i < ds.Length; i++) {
Calculate c = (Calculate)ds[i];
int ret = c(3, 4); // ret就是每一个子委托的返回值
}
5. 系统定义好的委托
5.1 Action
Action是无返回值的委托
public delegate void Action();
public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
// 1. 无参无返回值
public void Test1()
{
Action a1 = Fun;
}
public void Fun()
{
print("无参无返回值");
}
// 2. 有参无返回值
public void Test2()
{
Action<int, string> a2 = Fun2;
}
public void Fun2(int a, string b)
{
print("有参无返回值");
}
5.2 Func
Func是有返回值的委托
记忆:Func的最后一个类型参数是返回类型,而前面的都是输入参数类型
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T1, out TResult>(T1 arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
// 1. 无参有返回值
public void Test3()
{
Func<int> a3 = Fun3;
}
public int Fun3()
{
return 0;
}
// 2. 有参有返回值
public void Test4()
{
Func<int, string> a4 = Fun4;
}
public string Fun4(int a)
{
return "有参有返回值";
}
6. 委托存在的意义
每当我们打开一个游戏后,游戏菜单主界面都有很多按钮,不同的按钮触发不同的事情。如:开始游戏,退出游戏,设置等操作按钮。如果每一个按钮都要实现一个类的话,那么会造成类的耦合,如下面这个代码:
class StartButton {
public void StartGame(){
Game game = new Game(); // 关联了Game类,造成了耦合
game.Start();
}
}
但是如果使用委托的话,就不会出现这个问题。如下面的代码,不仅代码简洁,而且没有耦合度。当我们要用到Button是,直接new Button。为这个Button添加你想要做的事情。
而且如果你想要将Button放在其他的项目,也是没有问题的。
这就是委托的意义。
public class Button // 定义一个Button类
{
public delegate void OnclickDelegate(); // 定义一个委托
public OnclickDelegate onClick = null; // 声明一个委托
public void Click()
{
Console.WriteLine("点击了这个按钮");
if (onClick != null)
onClick.Invoke();
}
}
class Program
{
static void Main(string[] args)
{
// 我要点击 开始游戏的按钮
Button button = new Button();
button.onClick = new Button.OnclickDelegate(StartGame); // 这个按钮处理 开始游戏的逻辑
button.Click();
// 我要点击 退出游戏的按钮
Button button1 = new Button();
button1.onClick = new Button.OnclickDelegate(EndGame); // 这个按钮处理 退出游戏的逻辑
button1.Click();
Console.ReadKey();
}
public static void StartGame()
{
Console.WriteLine("处理开始游戏逻辑");
}
public static void EndGame()
{
Console.WriteLine("处退出始游戏逻辑");
}
}
事件
[1. 非常推荐一个视频,讲得非常透彻,我认为最好的讲解事件的视频](https://www.bilibili.com/video/BV1ou411a7YD/?spm_id_from=333.788&vd_source=b5ee6f0d5223b168ab79161524bb903d)
[2. 对事件的练习视频,非常推荐](https://www.bilibili.com/video/BV13X4y1479i/?spm_id_from=333.1007.top_right_bar_window_history.content.click)
事件是一种设计模式(通知模型、观察者模式、监听模型)
事件其实也就是委托,但比委托更加的安全。
表示类的偶发的行为(偶发:就是偶尔发生的行为)
一旦这个行为发生,往往要引起其它行为【方法】的自动执行
1. 事件源 响应者
事件源:定义事件的类
响应者(订阅者):行为2所在的类
比如:模拟主人喂食宠物的过程,三个宠物:猫/狗/熊猫。
事件源:主人(OnFeed喂食方法,参数) 不同的宠物吃的食物不同,所以需要一个参数
订阅者:猫/狗/熊猫(Eat 吃方法)
事件:主人喂食,宠物们吃
比如:张三骂李四,李四打张三。张三是事件源,李四是响应者。因为张三骂了李四,所以李四打了张三。两者之间有着一定的因果关系
比如:我生病了,我去医院。我即是事件源,也是响应者。因为我生病了,所以我去了医院
2. 事件的语法
访问修饰符 event 委托类型 事件名;
3. 事件的使用
委托怎么用,事件就怎么用
class Test{
//委托成员变量 用于存储 函数的
public Action myFun;
//事件成员变量 用于存储 函数的
public event Action myEvent;
}
4. 事件相对于委托的区别
1.不能在类外部赋值,但是可以通过 加减 去添加或移除记录的函数
2.不能在类外部调用, 只能通过类的内部去封装,再调用
匿名函数
没有名字的函数,必须要结合委托和事件进行
1. 匿名函数的基本语法
delegate (参数列表) { 逻辑块 };
2. 匿名函数的使用
2.1 无参无返回
Action a1 = delegate ()
{
print("无参无返回");
};
2.2 有参无返回
Action<int> a2 = delegate (int x)
{
print(x);
};
2.3 有返回值
Func<string, string> a3 = delegate (string x)
{
return x;
};
2.4 匿名函数作为函数参数传递
public void Test2()
{
Fun(delegate (int a)
{
print("这是什么时候执行");
});
}
public void Fun(Action<int> a)
{
int x = 0;
a.Invoke(x);
}
2.5 匿名函数作为函数返回值
public Func<int, int> Fun2(int x)
{
return delegate (int y)
{
return x * y;
};
}
3. 匿名函数的缺点
3.1 无法移除指定函数,但可以移除所有的函数
public void Tets3()
{
Action myFuns = null;
myFuns += delegate ()
{
print("功能1");
};
myFuns += delegate ()
{
print("功能2");
};
// 无法移除指定函数,但可以移除所有的函数
myFuns = null;
}
Lambda表达式
匿名函数的简写,结合事件和委托使用
1. Lambda的语法
(参数列表)=> { 逻辑块 };
2. Lambda的使用
2.1 无参无返回
Action a1 = () =>
{
print("无参数");
};
2.2 有参数
Action<int> a2 = (int a) =>
{
print(a);
};
2.3 省略参数类型
Action<int, string> a3 = (a, b) =>
{
print(a);
print(b);
};
2.4 有返回值
Func<int> a4 = () =>
{
int a = 10;
return a;
};
2.5 有参数,有返回值
Func<string, string> a5 = (a)=>{
return a;
};
3. 闭包
3.1 内层的函数可以引用包含在它外层的函数的变量
下面的代码中的value一但被包裹,将会改变value的生命周期。
value正常的生命周期是函数执行完,会自动销毁。
value被包含后,不会随着函数执行完自动销毁,会一直存在。
public void Test2()
{
int value = 10;
Action a6 = () =>
{
print(value);
};
a6();
}
3.2 该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
下面代码中打印的值会一直是9
public void Test3()
{
Action a7 = null;
for(int i = 0; i < 10; i++)
{
a7 += () =>
{
print((int)i);
};
}
a7();
}
评论区