C# Advanced Tutorial 1-6-Delegate


主要内容概要

  1. 委托的声明、实例化和调用
  2. 泛型委托–Func Action
  3. 委托的意义:解耦
  4. 委托的意义:异步多线程
  5. 委托的意义:多播委托
  6. 事件 观察者模式

委托

委托的声明

委托在IL 中就是一个类,继承自父类(特殊类)MulticastDelegate
MulticastDelegate是特殊类, 不能被继承
DelegateClassIL
声明委托的方法:

//委托声明 可以声明在类的外面也可以是在类内部
public delegate void NoReturnNoPara();
public delegate void NoReturnWithPara(int x, int y);//1 声明委托
public delegate int WithReturnNoPara();
public delegate int WithReturnWithPara(out int x, ref int y);

委托的实例化

假设预先定义好一个方法,是无参无返回值的,如下:

private void DoNothing()
{
  Console.WriteLine("This is DoNothing");
}

然后实例化的时候要注意:要求传递一个和委托参数返回值完全一致的方法。

委托的调用

思考:委托是一个类 为什么要用委托呢?
这个类的实例可以放入一个方法,实例invoke,就是调用方法,这看上去还是在执行方法,为什么要分成三个步骤呢?
唯一的差别,就是把方法放入一个对象/变量。

NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);
method.Invoke();// 就是去调用方法
method();// 可以省略Invoke
this.DoNothing(); //和委托Invoke 是做的同一件事
method.BeginInvoke(null, null);//这里是开启一个新的线程去执行方法
method.EndInvoke(null);//等待一个异步调用的结束

下面举一个例子来说明这样做的好处:
有一个学生类,里面有一些方法,需要根据不同的地区有不同的打招呼方式:

public class Student
{
  public int Id { get; set; }
  public string Name { get; set; }
  public int ClassId { get; set; }
  public int Age { get; set; }
  public enum PeopleType
  {
    Chinese = 1,
    American = 2,
    Japanese = 3
  }

  public void SayHi(string name, PeopleType peopleType)
  {
    Console.WriteLine("招手");
    switch (peopleType)
    {
      case PeopleType.Chinese:
          Console.WriteLine($"{name}:晚上好!");
          break;
      case PeopleType.American:
          Console.WriteLine($"{name}:good evening!");
          break;
      case PeopleType.Japanese:
          Console.WriteLine($"{name}:#@%¥%……%%…!");
          break;
      default://遇到不认识的枚举,说明调用有问题,那就不要隐藏,直接异常
          throw new Exception("枚举不存在!");
    }
  }
}

中国人:晚上好
美国人:good evening!
通过枚举判断,执行各自的业务逻辑
如果新增一个国家的人,那么就修改这个方法
如果业务升级,方法需要频繁的修改,方法很不稳定
传参数peopleType 是判断之后执行逻辑,传参数是为了执行逻辑,参数不同逻辑又不一样,既然如此,能不能把这个逻辑直接传过来呢?逻辑是什么?逻辑不就是方法吗?如果把一个方法包裹之后传递呢? 委托

用委托之前先构造一些方法:

public void SayHiChinese(string name)
{
  Console.WriteLine("招手");
  Console.WriteLine($"{name}:晚上好!");
}

public void SayHiAmerican(string name)
{
  Console.WriteLine("招手");
  Console.WriteLine($"{name}:good evening!");
}

public void SayHiJapanese(string name)
{
  Console.WriteLine("招手");
  Console.WriteLine($"{name}:#@%¥%……%%…!!");
}

编写不同的方法,根据不同的人调用不同的方法 修改某个方法不影响别的方法;
和上面的实现方案比较好像好一点。
如果在问好之前需要做点什么?这里就需要在每个方法中增加一个动作,代码重复很多。
所以我们的出发点是说:既要增加公共逻辑方便,又要维护简单。

所以,再定义一个泛型委托:

public void SayHiShow(string name, Action<string> method)
{
  Console.WriteLine("招手");
  method.Invoke(name);
}

再看一下这个过程,委托的作用:
自上而下比较:逻辑解耦,方便维护升级,代码稳定!
自下往上:去掉重复代码 代码重用。

泛型委托

系统内置的委托:

Action Func 从.Netframework 3.0出现,是系统提供的委托

  • Action:没有返回值, 参数可有可无
  • Func: 必须有返回值,参数可有可无

Action: 没有返回值,最多支持16个泛型参数:
Action act = new Action(this.DoNothing);或者可以直接这么写Action act = this.DoNothing;这是个语法糖。

Action<int> actInt = new Action<int>(NoReturn);
Action<int, string, DateTime, object, int, string, DateTime, object, int, string, DateTime, object, int, string, DateTime, object> action = null;

Func: 有(泛型)返回值,最多支持16个泛型参数,泛型参数列表的最后一个为委托返回值类型:
Func func1 = new Func(ToInt);
Func<int, int, string, DateTime, object, int, string, DateTime, object, int, string, DateTime, object, int, string, DateTime, object> Func2 = null;
int iResult = func1.Invoke();

我们其实可以自定义委托呀,那系统为什么还要定义这两个委托呢?
先看一个例子:

Action action0=this.DoNothing();
NoReturnNoPara method = this.DoNothing();
//定义一个方法 要求传入一个Action委托
private void DoAction(Action act)
{
  act.Invoke();
}
this.DoAction(action0);//传入action0没问题
this.DoAction(method);//传入method方法是不行的

委托的本质是类,Action和NoReturnNoPara是不同的类,虽然实例化都可以传递相同的方法,但是没有父子关系,所以不能替换的。
更进一步,框架中出现过N多个委托,委托的签名是一致的,实例化完的对象却不能通用;
本省实例化的时候,参数是一样的,但却是不同的类型,导致没法通用;
Action和Func 框架预定义 新的API一律基于这些委托来封装。
因为 .Net 向前兼容,以前的版本去不掉,保留着,这是历史包袱;
至此,就可以用通用的委托来使用。

new Thread(new ThreadStart(DoNothing));
new Thread(new NoReturnNoPara(DoNothing)); // 是不是重复声明?

*多种途径实例化

//普通方法实例化委托
NoReturnNoPara method1 = new NoReturnNoPara(this.DoNothing);

//静态方法实例化委托
NoReturnNoPara method1 = new NoReturnNoPara(DoNothingStatic);

//实例方法实例化委托
NoReturnNoPara method1 = new NoReturnNoPara(new Student().Study);

//静态方法实例化委托
NoReturnNoPara method1 = new NoReturnNoPara(Student.StudyAdvanced);

多播委托

多播委托 我们自定义的任何一个委托都是多播委托;
可以通过+=/-= 向委托中增加或者移除方法;
+=增加方法以后,让委托组成了一个方法链;
-=只能移除同一个实例里的方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除,且只移除一个。如过没有可以移除的方法,就啥事儿也不发生;

下面是一些例子:

var student = new Student();
Action method1= this.DoNothing;
method1 += this.DoNothing;
method1 += student.Study; //加括号就是调用    
method1 -= this.DoNothing;//this 表示当前实例的方法
method1 -= student.Study; //仍然会执行 因为是两个不同的实例 不完全吻合
method1.Invoke();

foreach (NoReturnNoPara item in method1.GetInvocationList())
{
  item.Invoke();
  item.BeginInvoke(null,null);//异步来执行
}

如果func 委托在+= 形成一个方法链以后,在执行后,只能获取最后一个方法的返回值。多播委托一般不要返回值,因为前面几个的返回值会丢失。
多播委托不能异步,会报错。如果要异步执行的话,可以用上述foreach中的GetInvocationList方法来分别执行。
中间出现未捕获的方法,直接出现方法链结束。


文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
C# Advanced Tutorial 1-7-Event C# Advanced Tutorial 1-7-Event
主要内容概要一只猫的故事这里有个例子,一只猫叫了一声,会有一系列的动作,如下: public void Miao() { Console.WriteLine("{0} Miao", this.GetType().Name); new
下一篇 
C# Advanced Tutorial 1-5-Attribute C# Advanced Tutorial 1-5-Attribute
主要内容概要 特性attribute,和注释有什么区别 声明和使用attribute,AttributeUsage 运行中获取attribute:额外信息 额外操作 Remark封装、attribute验证 特性及其语法MVC-EF-WC
  目录