一:什么叫委托
通过反射发现,委托其实是一个类,继承自System.MulticastDelegate,但是System.MulticastDelegate这个类是特殊类,不能被继承
二:委托的声明
public delegate void NoReturnNoParaOutClass();
public class MyDelete
{
public delegate void NoReturnNoPara<T>(T t);
public delegate void NoReturnNoPara();
public delegate void NoReturnWithPara(int x, int y);
public delegate int WithReturnNoPara();
public delegate string WithReturnWithPara(out int x, ref int y);
}
委托可以声明在类外面,可以声明再类里面
三:委托的实例和调用
private int GetSomething()
{
return ;
}
private int GetSomething2()
{
return ;
} private int GetSomething3()
{
return ;
}
private void DoNothing()
{
Console.WriteLine("This is DoNothing");
}
private static void DoNothingStatic()
{
Console.WriteLine("This is DoNothingStatic");
}
public string ParaReturn(out int x, ref int y)
{
throw new Exception();
}
//多种途径实例化,要求传递一个参数类型,返回值都跟委托一致的方法
{
WithReturnWithPara method = new WithReturnWithPara(ParaReturn);
int x = ;
int y = ;
var dd = method.Invoke(out x, ref y);
}
//begininvoke
{
WithReturnNoPara method = new WithReturnNoPara(this.GetSomething);
int iResult = method.Invoke();
iResult = method();
var result = method.BeginInvoke(null, null);//异步调用
method.EndInvoke(result);
}
{
NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);
//委托实力的调用,参数和委托约束的一致
method.Invoke(); //1
//method(); //2
//method.BeginInvoke(null, null); //3
//this.DoNothing(); //1,2,3都等同于this.DoNothing }
{
NoReturnNoPara method = new NoReturnNoPara(DoNothingStatic);
}
{
NoReturnNoPara method = new NoReturnNoPara(Student.StudyAdvanced);
}
{
NoReturnNoPara method = new NoReturnNoPara(new Student().Study);
}
四:为什么要使用委托
有时候我们声明一个方法,直接调用蛮好的,为啥还要使用委托,然后还要先声明,再实例化,再inovke调用呢?
下面我们举个例子,比如一个人问好这件事情,不同人问候方式不一样,我们会先定义一个类型,如枚举
public enum PeopleType
{
Chinese,
America,
Japanese
}
然后通过不同的类型来判断问候方式不同,如下
/// 为不同的人,进行不同的问候
/// 传递变量--判断一下----执行对应的逻辑
/// </summary>
/// <param name="name"></param>
/// <param name="peopleType"></param>
public void SayHi(string name, PeopleType peopleType)
{
switch (peopleType)
{
case PeopleType.Chinese:
Console.WriteLine($"{name}晚上好");
break;
case PeopleType.America:
Console.WriteLine($"{name},good evening");
break;
case PeopleType.Japanese:
Console.WriteLine($"{name},&&%*^^***@@@&&&&");
break;
default:
throw new Exception("wrong peopleType"); //遇到异常报错
}
}
这样做的好处是:以后如果增加公共逻辑等比较容易,但是如果类型比较多,这个方法会变成无限制改动,导致方法难以维护,于是很多人想着增加分支,就增加方法--不影响别的方法的思路来改善
public void SayHiChinese(string name)
{
Console.WriteLine($"{name}晚上好");
}
public void SayHiJapanese(string name)
{
Console.WriteLine($"{name},&&%*^^***@@@&&&&");
}
public void SayHiAmerican(string name)
{
Console.WriteLine($"{name},good evening");
}
然后上层判断调用
这样做的好处是:修改某个方法--不影响别的方法 ,但是缺点却是:增加公共逻辑---多个方法就有很多重复代码
那么我们想:既增加逻辑方便,又维护简单,鱼肉熊掌,如何兼得呢?
我们可以把相应的逻辑做为参数传进来,这样就解决了我们的问题
具体我们可以按照以下来做:
public void SayHiPerfact(string name, SayHiDeletegate method)
{
Console.WriteLine("增加开始日志");
method.Invoke(name);
Console.WriteLine("增加结束日志");
}
public delegate void SayHiDeletegate(string name);
然后调用的时候如下:
SayHiDeletegate method = new SayHiDeletegate(SayHiChinese);
这样就做到了
1:逻辑解耦,方便维护
2:代码重构,去掉重复
其实这也是我们选择使用委托的两大优点
注意:以上我们纯粹为了定义委托而定义委托,其实框架已经我们帮我们定义了Action 和Func这两个委托,Action是没有返回值,Func是有返回值的,这两个委托类已经足够我们使用了,所以有时候我们使用的时候,没有必要自己再去定义,而直接使用即可
1:Action: 系统提供的,0-16个泛型参数,不带返回值的 委托
//Action 系统提供的,0-16个泛型参数,不带返回值的 委托
Action action0 = new Action(DoNothing);
Action action1 = this.DoNothing; //语法糖,就是编译器帮我们添加上new Action
Action<int> action2 = this.ShowInt;
2:Func 系统提供的,0-16个泛型参数,带一个返回值的 委托
//Func 系统提供的,0-16个泛型参数,带返回值的 委托
Func<int> func = this.GetSomething2;
func.Invoke(); Func<int, string> func1 = this.ToString;
func1.Invoke();
3:系统或者框架为什么要封装这样的方法?第一步我们定义一个这样的方法,需要传入一个Action
private void DoAction(Action act)
{
act.Invoke();
}
然后我们可以这样调用:
Action action0 = this.DoNothing;
NoReturnNoPara method = this.DoNothing;
// this.DoAction(method); //这样就不行,因为类型不一致
this.DoAction(action0);
但是我们不能使用 this.DoAction(method),因为:委托的本质是类,action和NoReturnNoPara是不同的类,虽然实例化都可以传递相同的方法,但是没有父子关系,所以不能替换,就像student和teacher两个类,实例化都是传递id/name,但是二者不能替换
所以:框架提供这种封装,自然是希望大家都统一使用Action/Func,但是之前还有很多委托,至今再框架中还能看到,因为.net向前兼容,以前的版本去不掉的,保留着,这是历史包袱,此后我们就不要定义新的委托了!
五:多播委托
委托是一个类,然后继承于MulticastDelegate(多播委托),但是这个类是特殊类,所以任何一个委托都是多播委托类型的子类
{
//多播委托:任何一个委托都是多播委托类型的子类,可以通过+=去添加方法
//多播委托如果中间出现未捕获的异常,方法链直接结束
//多播委托:一个变量保存多个方法,可以增减;invoke的时候可以按顺序执行
//+= 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行系列方法
Student studentNew = new Student(); NoReturnNoPara method = new NoReturnNoPara(this.DoNothing); //普通的DoNothing方法
method += new NoReturnNoPara(this.DoNothing); //普通的DoNothing方法
method += new NoReturnNoPara(DoNothingStatic); //静态方法
method += new NoReturnNoPara(Student.StudyAdvanced); //静态方法
method += new NoReturnNoPara(new Student().Study);//不是同一个实例,所以是不同的方法
method += new NoReturnNoPara(studentNew.Study);
method.Invoke();
//method.BeginInvoke(null, null);//委托里面如果有多个Target,则不能异步调用,多播委托是不能异步的 foreach (Action item in method.GetInvocationList()) //得到委托实例的target方法 ,静态方法target为null
{
item.Invoke();
//item.BeginInvoke(null, null);
} //-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常
method -= new NoReturnNoPara(this.DoNothing);
method -= new NoReturnNoPara(DoNothingStatic);
method -= new NoReturnNoPara(Student.StudyAdvanced);
method -= new NoReturnNoPara(new Student().Study); //去不掉,原因是不同的实例的相同方法,并不吻合
method -= new NoReturnNoPara(studentNew.Study);
method.Invoke();
}
{
WithReturnNoPara method = new WithReturnNoPara(this.GetSomething);
method += new WithReturnNoPara(this.GetSomething2);
method += new WithReturnNoPara(this.GetSomething3);
int iResult = method.Invoke();//多播委托带返回值,结果以最后的为准,所以一般多播委托用的是不带返回值的
}
注意:
+= 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行系列方法
-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合(new Student().Study如果声明两次则不属于吻合)的,移除且只移除一个,没有也不异常
多播委托其实就是观察者模式的缩影。
比如一只猫叫了一声,然后需要触发一系列的动作,比如老鼠跑,孩子哭,狗叫等等
如果我们把这些都写在猫的miao的方法中,如下:
public void Miao()
{
Console.WriteLine("{0} Miao", this.GetType().Name); new Mouse().Run();
new Baby().Cry();
new Mother().Wispher();
//new Brother().Turn();
new Father().Roar();
new Neighbor().Awake();
new Stealer().Hide();
new Dog().Wang();
}
上面的代码:
依赖太重,依赖多个类型,任何类型的变化都得修改猫
职责耦合,猫不仅自己miao,还得找各种对象执行各种动作甚至控制顺序
任意环节增加或者减少调整顺序, 都得修改猫
那么我们可以修改代码,然后Cat的这类中,增加一个委托,然后猫只是自己叫,具体的其他的动作,只需要执行这个多播委托即可,代码如下:
//猫 叫一声 触发一系列后续动作
//多了个 指定动作 正是这个不稳定 封装出去 甩锅
public MiaoDelegate MiaoDelegateHandler;
public void MiaoNew()
{
Console.WriteLine("{0} MiaoNew", this.GetType().Name);
if (this.MiaoDelegateHandler != null)
{
this.MiaoDelegateHandler.Invoke();
}
}
然后外面调用的时候可以直接通过下面:
{
Cat cat = new Cat();
cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run);
cat.MiaoDelegateHandler += new MiaoDelegate(new Baby().Cry);
cat.MiaoDelegateHandler += new MiaoDelegate(new Mother().Wispher);
cat.MiaoDelegateHandler += new MiaoDelegate(new Brother().Turn);
cat.MiaoDelegateHandler += new MiaoDelegate(new Father().Roar);
cat.MiaoDelegateHandler += new MiaoDelegate(new Neighbor().Awake);
cat.MiaoDelegateHandler += new MiaoDelegate(new Stealer().Hide);
cat.MiaoDelegateHandler += new MiaoDelegate(new Dog().Wang);
cat.MiaoNew();
Console.WriteLine("***************************");
}
这样猫就减少了依赖,而且猫叫后,各种动作的顺序也可以随便改变,而不会改变猫叫的方法!
六:事件
事件:是带event关键字的委托的实例,event可以限制变量被外部调用/直接赋值
event:限制权限,只允许在事件声明类里面去invoke和赋值,不允许外面,甚至子类都能运用
event事件只能声明在类中,而委托可以声明在类外面
比如我们在cat类中定义一个事件
public event MiaoDelegate MiaoDelegateHandlerEvent;
public void MiaoNewEvent()
{
Console.WriteLine("{0} MiaoNewEvent", this.GetType().Name);
if (this.MiaoDelegateHandlerEvent != null)
{
this.MiaoDelegateHandlerEvent.Invoke();
}
}
然后只能在cat类中中进行invoke调用,如果我们定义一个子类继承cat类,如下:
public class ChildClass : Cat
{
public void Show()
{
this.MiaoDelegateHandlerEvent += null;
if (this.MiaoDelegateHandlerEvent != null)//子类也不能调用,调用报错
{
this.MiaoDelegateHandlerEvent.Invoke();
}
}
}
this.MiaoDelegateHandlerEvent.Invoke(); 这样是不能使用的,Invoke这个是完全不能调用的!
委托和事件的区别与联系?
委托是一个类型,比如Student
事件是委托类型的一个实例,加上了event的权限控制 比如学生加菲猫,是一个确切的实体
然后事件也可以通过下面来调用多个方法
{
Cat cat = new Cat();
//cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Baby().Cry);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mother().Wispher);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Brother().Turn);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Father().Roar);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Neighbor().Awake);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Stealer().Hide);
cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Dog().Wang);
cat.MiaoNewEvent();
Console.WriteLine("***************************");
}
下面写一个标准的的事件,事件一般分为三种:
1:事件的发布者:发布事件,并且在满足条件时候触发事件
2:事件的订户:关注事件,事件发生后,自己做出相应的动作
3:事件的订阅:把订户和发布者的事件关联起来
/// <summary>
/// 委托是一种类型,静态类不能被继承,所以委托不能声明静态,
/// event只是一个实例,所以可以生成实例
/// </summary>
class EventStandard
{
/// <summary>
/// 订阅:把订户和发布者的事件关联起来
/// </summary>
public static void Show()
{
iPhoneX phone = new iPhoneX()
{
Id = ,
Tag = "1.0"
};
// 订阅:把订户和发布者的事件关联起来
phone.DiscountHandler += new Student().Buy;
phone.DiscountHandler += new Teacher().Notice; phone.Price = ; }
/// <summary>
/// 订户:关注事件,事件发生后,自己做出对应的动作
/// </summary>
public class Student
{
public void Buy(object sender, EventArgs e)
{
iPhoneX phone = (iPhoneX)sender;
Console.WriteLine($"this is {phone.Tag} iphoneX");
XEventArgs args = (XEventArgs)e;
Console.WriteLine($"之前的价格{args.OldPrice}");
Console.WriteLine($"限制的价格{args.NewPrice}");
Console.WriteLine("立马买!!");
}
}
public class Teacher
{
public void Notice(object sender, EventArgs e)
{
iPhoneX phone = (iPhoneX)sender;
Console.WriteLine($"this is {phone.Tag} iphoneX");
XEventArgs args = (XEventArgs)e;
Console.WriteLine($"之前的价格{args.OldPrice}");
Console.WriteLine($"限制的价格{args.NewPrice}");
Console.WriteLine("立马买!!");
}
} /// <summary>
/// 事件参数 一般会为特定的事件去封装个参数
///订户:Teacher/Student:关注事件,事件发生后,自己做出对应的动作
/// </summary>
public class XEventArgs : EventArgs
{
public int OldPrice { set; get; }
public int NewPrice { set; get; }
} /// <summary>
/// 事件的发布者,发布事件,并且在满足条件时候触发事件
/// </summary>
public class iPhoneX
{
public int Id { set; get; }
public string Tag { set; get; }
public int Price
{
set
{
if (value < this._price)
{
this.DiscountHandler?.Invoke(this, new XEventArgs() { OldPrice = this._price, NewPrice = value });
this._price = value;
}
}
get { return this._price; }
} private int _price; /// <summary>
/// 打折事件
///
/// </summary>
public event EventHandler DiscountHandler;
}
}
EventHandler:框架自带,表示将用于处理不具有事件数据的事件的方法。EventHandler(object sender, EventArgs e)
下面这个例子也很经典
/// <summary>
/// 妈妈做好饭,触发爸爸和儿子一起吃饭,并且妈妈会把对应的菜单同时告知爸爸和儿子
/// </summary>
public class MyEvenStandard
{
public class Meal
{
public static void FinishMeal()
{
Mom mom = new Mom();
mom.EatHandler += (new Dad()).Eat;
mom.EatHandler += (new Son()).Eat;
mom.Cook();
}
} public class Mom
{
//EventHandler(object sender, EventArgs e) 事件的原型,里面sender则是发布者自己,e:是发布者的交互参数
public event EventHandler EatHandler;
public void Cook()
{
Console.WriteLine("开始吃饭了");
EatHandler?.Invoke(this, new MenuArags() { Fruid = "orange", Greens = "green pepper", Meat = "fish" });
}
}
/// <summary>
/// 爸爸
/// </summary>
public class Dad
{
public void Eat(object sender, EventArgs args)
{
var menArgs = (MenuArags)args;
Console.WriteLine($"儿子,我们今天晚上次:{menArgs.Greens},{menArgs.Fruid},{menArgs.Meat}");
}
} /// <summary>
/// 儿子
/// </summary>
public class Son
{
public void Eat(object sender, EventArgs args)
{
var menArgs = (MenuArags)args;
Console.WriteLine($"爸爸,我们今天晚上次:{menArgs.Greens},{menArgs.Fruid},{menArgs.Meat}");
}
} public class MenuArags : EventArgs
{
/// <summary>
/// 水果
/// </summary>
public string Fruid { set; get; } /// <summary>
/// 青菜
/// </summary>
public string Greens { set; get; } public string Meat { set; get; }
} }