委托
委托的定义和使用
- 定义
- 委托是一个类型,这个类型可以赋值一个方法的引用
- 首先定义委托,需要告诉编译器我们这个委托可以指向哪些类型的方法,要定义方法的参数和返回值(方法的签名),使用关键字delegate定义,然后创建该委托的实例
- 委托类型即可以引用静态方法,也可以引用普通方法
- 使用
delagate void IntMethodInvoker(int x);
class Program
{
private delegate string GetAString();
static void Main(string[] args)
{
int x = 40 ,y=50; //所有的类型都是继承object的
//string str = x.ToString(); //tostring用来将数据转换为字符串
//通过委托类型调用一个方法,跟直接调用这个方法作用是一样的
//初始化的两种方法,使用委托类型创建实例,使de指向tostring方法
GetAString de1 = new GetAString(x.ToString);
GetAString de2 = y.ToString;
//调用的两种方法
Console.WriteLine(de1());
Console.WriteLine(de2.Invoke());
Console.ReadKey();
}
}
- 委托类型可以当作参数传递
class Program
{
static void Main(string[] args)
{
//委托类型可以当作参数传递
PrintString method = Method1; //赋初值,使method指向Method1
PrintStr(method);
method = Method2;
PrintStr(method);
Console.ReadKey();
}
private delegate void PrintString();
static void PrintStr(PrintString print)
{
print();
}
static void Method1()
{
Console.WriteLine("Method1");
}
static void Method2()
{
Console.WriteLine("Method2");
}
}
Action委托
- 除了我们自己定义的委托之外,系统还给我们提供内置的委托类型。Action委托可以指向一个没有返回值,没有参数的方法。
class Program
{
static void Main(string[] args)
{
//Action是系统内置(预定义)的一个委托类型,它可以指向一个没有返回值/参数的方法
Action action1 = PrintString;
action1();
//泛型Action委托,可以指向一个指定类型参数的方法
Action<int> action2 = PrintInt;
action2(2);
//若方法重名,系统会自动匹配泛型Action的参数类型相符合的方法
Action<string> action3 = PrintString;
action3("JOJO");
//多个参数,可以有0-16个参数类型
Action<string, int> action4 = PrintMix;
action4("JOJO", 4);
Console.ReadKey();
}
static void PrintString()
{
Console.WriteLine("PrintString");
}
static void PrintInt(int x)
{
Console.WriteLine(x);
}
static void PrintString(string str)
{
Console.WriteLine(str);
}
static void PrintMix(string str,int x)
{
Console.WriteLine(str + x);
}
}
Func委托
- Func委托要求指向的方法必须得有一个返回值。它可以传递0-16个参数类型,和一个返回类型。
class Program
{
static int Test1()
{
return 100;
}
static int Test2(string str)
{
Console.WriteLine(str);
return 2333;
}
static int Test3(int i, int j)
{
return i + j;
}
static void Main(string[] args)
{
Func<int> func1 = Test1;
Console.WriteLine(func1());
//Func后面可以跟很多类型,但最后一个是返回值类型
Func<string, int> func2 = Test2;
Console.WriteLine(func2("bilibli"));
Func<int, int, int> func3 = Test3;
Console.WriteLine(func3(2, 4));
Console.ReadKey();
}
}
int类型的冒泡排序
- 对集合进行排序,冒泡排序
namespace _028_冒泡排序拓展
{
class Program
{
static void Sort(int[] sortArray) //对集合进行排序,冒泡排序
{
bool swapped = true;
do
{
swapped = false;
//每一次for循环就交换两个数,到最后没有数可以交换了就推出while循环
for (int i = 0; i < sortArray.Length - 1; i++)
{
if (sortArray[i] > sortArray[i + 1])
{
int temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}
static void Main(string[] args)
{
int[] sortArray = { 12, 31, 54, 87, 61, 3, 798, 46, 4, 6 };
Sort(sortArray);
foreach (var temp in sortArray)
{
Console.Write(temp + " ");
}
Console.ReadKey();
}
}
}
拓展:通用的冒泡排序
题目:比较雇员的工资,然后排序输入
Employee类
namespace _028_冒泡排序拓展
{
class Employee
{
//定义雇员类的属性
public string Name { get; set; }
public int Salary { get; set; }
public Employee(string name, int salary)
{
this.Name = name;
this.Salary = salary;
}
//创建方法处理自身类型的比较
//如果e1大于e2则返回true,否则返回false
public static bool Compare(Employee e1, Employee e2)
{
if (e1.Salary > e2.Salary) return true;
return false;
}
//默认输出当前的类型:命名空间+类名(继承object类的),可以选择重写
public override string ToString()
{
return Name + " " + Salary;
}
}
}
Main方法
namespace _028_冒泡排序拓展
{
class Program
{
//泛型+委托
//因为排序的数据类型不确定,所以使用泛型
//重要!!:如果要比较其他的属性,需要重写Compare方法,然后当作委托类型的参数进行传递(当作排序的if条件)
static void CommonSort<T>(T[] sortArray,Func<T,T,bool>compareMethod)
{
//每次比较的时候,把数组传递过来,还有一个比较的方法
//基本的判断方法没有变化,还是冒泡排序的方法
bool swapped = true;
do
{
swapped = false;
for (int i = 0; i < sortArray.Length - 1; i++)
{
if (compareMethod(sortArray[i],sortArray[i+1]))
{
T temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}
static void Main(string[] args)
{
Employee[] employees = new Employee[]//Employee类型的数组
{
new Employee("1A",22),
new Employee("2A",2345),
new Employee("2B",35),
new Employee("9S",223),
new Employee("S",75)
};
//泛型类型为Employee,传入的数组也是Employee类型,所以foreach的时候需要声明一个对象
CommonSort<Employee>(employees, Employee.Compare);
foreach (Employee em in employees)
{
Console.WriteLine(em.Name +":"+ em.Salary);
}
//也可以重写ToString方法
//foreach (Employee item in employees)
//{
// Console.WriteLine(item.ToString());
// //Console.WriteLine(item); 默认会输出item.ToString()
//}
Console.ReadKey();
}
}
}
多播委托
- 前面使用的委托都只包含一个方法的调用,但是委托也可以包含多个方法,这种委托叫做多播委托。使用多播委托就可以按照顺序调用多个方法,多播委托只能得到调用的最后一个方法的返回值,所以一般我们把多播委托的返回值声明为void。
- 多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出异常,整个迭代就会停止。
namespace _029_多播委托
{
class Program
{
static void Test1()
{
Console.WriteLine("Test1");
//throw new Exception();
//抛出异常,多播委托要是先调用的方法出现异常,后面的不执行
}
static void Test2()
{
Console.WriteLine("Test2");
}
//有参数、返回值的方法
static string Test3(int x)
{
Console.WriteLine(x);
return "Test3";
}
static string Test4(int y)
{
Console.WriteLine(y);
return "Test4";
}
static void Main(string[] args)
{
Action action1 = Test1;
action1 += Test2; //添加一个委托的引用
//action -= Test1; //删除一个委托的引用
//action -= Test2;
if (action1!=null) //当一个委托没有指向任何方法的时候,调用的会出现异常null
action1();
//引用多个 带有参数、返回值的方法,只会返回最后一个返回值
Func<int,string> action2 = Test3;
action2 += Test4;
Console.WriteLine(action2(2333));
//取得多播委托中所有方法的委托
//Delegate[] delegates = action.GetInvocationList();
//foreach (Delegate de in delegates)//遍历多播委托中所有的委托,然后单独调用
//{
// de.DynamicInvoke(null); //可以传递参数调用不同的方法
//}
Console.ReadKey();
}
}
}
Test1
Test2
2333
2333
Test4
匿名方法
//匿名方法,本质上是一个方法,只是没有名字
//任何使用委托变量的地方都可以使用匿名方法赋值
Func<int, int, int> plus = delegate (int arg1, int arg2) //关键字delegate
{
return arg1 + arg2;
}; //加分号
//相当于
static int Plus(int arg1,int arg2)
{
return arg1 + arg2;
}
Func<int,int,int> plus = Plus;
Lambda表达式
- Lambda表达式表示一个方法的定义,可以使用Lambda表达式代替匿名方法
- Lambda运算符“=>”的左边列出了需要的参数,如果是一个参数可以直接写a=>(参数名自己定义),如果多个参数就使用括号括起来,参数之间以","间隔
Func<int, int, int> test1 = (arg1, arg2) => //lambda表达式的参数不需要声明类型
{
return arg1 + arg2;
}; //加分号
Console.WriteLine(test1(20,30));
- 如果Lambda表达式只有一条语句,在方法内就不需要花括号和return语句,编译器会自动添加return语句
//lambda表示的参数只有一个的时候,可以不加上括号()
//当函数体语句只有一句的时候,可以不加大括号{},也可以不加return语句
Func<int, int> test2 = a => a + 1;
Func<int, int> test3 = (a) => { return a + 1; };
Console.WriteLine(test2(22));
Console.WriteLine(test3(22));
Lambda表达式外部的变量
- 通过Lambda表达式可以访问Lambda表达式外部的变量,但是如果不能正确使用,也会非常危险。(因为不但受到参数控制,还受到变量的控制,结果不可控)
int somVal = 5;
Func<int, int> test4 = x => x + somVal;
Console.WriteLine(test4(4));
事件
- 定义
- 事件(event)基于委托,为委托提供了一个发布/订阅机制,是类或对象 向其他的类或对象 通知发生的事情的 一种特殊签名的委托
- 事件的声明只能作为类里面的一个成员来声明,无法声明为局部,使用方法跟委托没什么区别
- 事件的声明
- public event 委托类型 事件名
- 事件使用event关键词来声明,他的返回类值是一个委托类型
- 通常事件的命名,以名字+Event作为它的名称,在编码中尽量使用规范命名,增加代码的可读性
namespace _031_事件
{
class Program
{
public delegate void MyDelegate();
//public MyDelegate mydelegate; //声明一个委托类型的变量,作为类的成员
public event MyDelegate mydelegate; //事件的声明只能作为类里面的一个成员来声明,无法声明为局部
static void Main(string[] args)
{
Program p = new Program();
p.mydelegate = Test1;
p.mydelegate();
Console.ReadKey();
}
static void Test1()
{
Console.WriteLine("Test1");
}
}
}
观察者设计模式-猫抓老鼠
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。
- 事件与委托的联系和区别
- 事件是一种特殊的委托,或者说是受限制的委托 ,是委托的一种特殊应用,只能使用"+=" "-="操作符,二者本质上是一个东西。(不能在类的外部调用,但是可以进行注册)
- event ActionHandler Tick; //编译成创建一个私有的委托示例,和施加在其上的add,remove方法
- event只允许用add,remove方法来操作,这导致了它不允许在类的外部被直接触发,只能在类的内部适合的时机触发。委托可以在外部被触发,但是别这么用。
- 使用中,委托常用来表达回调,是事件表达外发的接口。(给别人提供注册)
- 实例
- Cat类
namespace _032_观察者设计模式_猫捉老鼠
{
class Cat
{
private string name;
private string color;
public Cat(string name, string color)
{
this.name = name;
this.color = color;
}
/// <summary>
/// 猫的状态发生改变(被观察者的状态发生改变)
/// </summary>
//使用委托给观察者发消息
public event Action catCome; //声明一个事件(发布消息)
public void CatCome()
{
Console.WriteLine(color + "的" + name + "准备来抓老鼠啦!");
if (catCome != null) catCome();
}
//当新增了观察者的对象后,被观察者需要需要修改代码,这样代码维护起来比较麻烦,耦合度高(互相调用)
//被观察者类开发完成,功能固定后,尽量减少对其修改
//不在被观察者内调用观察者,因为其数量等都不确定
//被观察者模式发生改变,只有观察者自身知道
//public void CatComing(Mouse mouse1,Mouse mouse2,Mouse mouse3)
//{
// Console.WriteLine(color + "的" + name + "准备来抓老鼠啦!");
// mouse1.RunAway();
// mouse2.RunAway();
// mouse3.RunAway();
//}
}
}
- Mouse类
namespace _032_观察者设计模式_猫捉老鼠
{
/// <summary>
/// 观察者类:老鼠
/// </summary>
class Mouse
{
private string name;
private string color;
public Mouse(string name, string color,Cat cat)
{
this.name = name;
this.color = color;
//把自身的逃跑方法 注册进 猫里面(订阅消息)
cat.catCome += this.RunAway;
}
/// <summary>
/// 逃跑功能
/// </summary>
public void RunAway()
{
Console.WriteLine(color + "的" + name + "说:猫来啦,快跑啊~~");
}
}
}
- Main方法
namespace _032_观察者设计模式_猫捉老鼠
{
class Program
{
static void Main(string[] args)
{
Cat cat = new Cat("Tom", "蓝色");
Mouse mouse1 = new Mouse("Jack", "棕色",cat);
Mouse mouse2 = new Mouse("Jakkie", "黑色",cat);
Mouse mouse3 = new Mouse("Zack", "红色",cat);
//注册事件,把Mouse类的RunAway方法添加进Cat类里的catCome委托
//cat.catCome += mouse1.RunAway;
//cat.catCome += mouse2.RunAway;
//cat.catCome += mouse3.RunAway;
cat.CatCome();
//事件不能在类的外部触发,只能在类的内部触发(委托可以在类的外部被触发,很危险)
//cat.catCome();
//在cat中调用了观察者的方法,当观察者发生改变时,需要同时修改被观察者的代码
//cat.CatComing(mouse1,mouse2);
Console.ReadKey();
}
}
}