委托

委托是函数(方法)的容器,类似存放(变量)的容器

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();
}