星星之火

燎原之势不可挡
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

.NET知识梳理——5.委托

Posted on 2020-02-24 09:56  星星之火116  阅读(233)  评论(0编辑  收藏  举报

1. 委托

委托就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法。在IL中委托就是一个类。继承自System.MulticastDelegate 特殊类,不能被继承。

1.1        委托的声明、实例化、调用

1.1.1  声明

委托用deleate关键字修饰,只有方法名。

   public delegate string XFInfoDelegate(string name, int age);

1.1.2  实例化、调用

  XFInfoDelegate XFInfo = new XFInfoDelegate(GetXFInfo);

            XFInfo.Invoke("Olive", 116);

            XFInfo(“Olive”,116);//这两种方法都可以调用

实例化的限制就是方法的参数列表&返回值类型必须和委托约束的一致。

1.2        泛型委托

有了泛型委托,就有了一能适用于任何返回类型和任意参数(类型和合理的个数)的通用委托,Func 和 Action(Action Func  .NetFramework3.0出现的)。

delegate TResult Func <out TResult> ();
delegate TResult Func <in T, out TResult> (T arg);
delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);
... 一直到 T16
delegate void Action ();
delegate void Action <in T> (T arg);
delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
... 一直到 T16

1.2.1  Action

只有输入参数、无返回值。

Action<string, int> SayInfo = (name, age) => Console.WriteLine($"Action Lambda方法:{name} 的年龄是:{age}");//是一种语法糖。

可以直接SayInfo(“XF”,30)调用,

也可以SayInfo.BeginInvoke调用,异步,会在线程池中启动一个线程。

IAsyncResult actionResult = SayInfo.BeginInvoke("XF", 30, (r) => {

            if(r==null||r.AsyncState==null)

                {

                    Console.WriteLine("回调异常");

                }           

                Console.WriteLine($"Action 回调成功,结果为:{r.AsyncState}");//可以拿到传入的第三个参数

            }, "Action 密码:Olive");

1.2.2  Func

必须有返回值,最有一个参数类型为返回值类型。

Func<string, int, string> PeopleInfo = delegate (string name, int age)

{

   ThreadInfo("Func 委托 线程情况打印");

   return $"Func采用匿名方法:{name} 的年龄为:{age}";

};

可以直接PeopleInfo (“XF”,30)调用,

也可以PeopleInfo.BeginInvoke调用,异步,会在线程池中启动一个线程。

PeopleInfo.BeginInvoke("XF", 30, (r) => {

Console.WriteLine($"Func 回调成功,结果为:{ PeopleInfo.EndInvoke(r)}");

            }, “Func 密码:Olive”);

 

1.3        委托的意义

1.3.1  解耦

1.3.2  异步多线程

委托可以通过BeginInvoke的方法实现异步多线程。

1.3.3  多播委托

任何一个委托都是多播委托类型的子类,可以通过+=/-=去增加/移除方法,会形成方法链,Invoke时,会按顺序执行系列方法一个委托实例包含多个方法。

1.3.3.1      += 

给委托的实例添加方法,会形成方法链,Invoke时,会按顺序执行系列方法

1.3.3.2      -= 

给委托的实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除,且只移除一个,如果没有匹配,则无任何影响。(不同的实例的相同方法不能移除)

多播委托实例不能异步,如果需要异步,可以对委托实例单个方法进行异步调用。

一般情况下多播委托用的是不带返回值的,如果带返回值,则返回最后一个方法的返回值。

1.3.3.3      猫叫示例

1.3.3.3.1              Cat类

猫叫,引发一些列行为

public class Cat

    {

        /// <summary>

        /// 猫叫,并引发一系列的后续行为

        /// 类内调用其它类实例方法,依赖多个类型,任何类型变化都需调整

        /// </summary>

        public void Miao()

        {

            Console.WriteLine($"{this.GetType().Name} Miao");

            new Mouse().Run();

            new Dog().Wang();

            new Baby().Cry();

            Console.WriteLine("该睡觉了");

        }

}

1.3.3.3.2              IObject

所有的观察者都需要继承的一个接口

/// <summary>

    /// 为所有的观察者定义一个接口

    /// </summary>

    public interface IObject

    {

        void DoAction();

    }

1.3.3.3.3              Mouse类

/// <summary>

    /// 老鼠类,实现了IObject接口,方便后边作为观察者对象被添加

    /// </summary>

    public class Mouse:IObject

    {

        public void DoAction()

        {

            Run();

        }

        public void Run()

        {

            Console.WriteLine($"{this.GetType().Name} Run");

        }

}

1.3.3.3.4              Dog类

/// <summary>

    /// 狗类,实现了IObject接口,方便后边作为观察者对象被添加

    /// </summary>

    public class Dog:IObject

    {

        public void DoAction()

        {

            Wang();

        }

        public void Wang()

        {

            Console.WriteLine($"{this.GetType().Name} Wang");

        }

    }

1.3.3.3.5              Baby类

/// <summary>

    /// 儿童类,实现了IObject接口,方便后边作为观察者对象被添加

    /// </summary>

    public class Baby:IObject

    {

        public void DoAction()

        {

            Cry();

        }

        public void Cry()

        {

            Console.WriteLine($"{this.GetType().Name} Cry");

        }

    }

1.3.3.3.6              常规调用

Cat cat = new Cat();

cat.Miao();

1.3.3.3.7              委托实现

现在Cat类中定义一个委托,

  public Action CatMiaoAction;

同时定义一个触发委托的方法

public void MiaoDelegate()

        {

            Console.WriteLine($"{this.GetType().Name} MiaoDelegate");

            this.CatMiaoAction?.Invoke();

            Console.WriteLine($"{this.GetType().Name} MiaoDelegate OVER");

        }

为委托绑定多个方法,

   cat.CatMiaoAction += new Mouse().Run;

   cat.CatMiaoAction += new Dog().Wang;

   cat.CatMiaoAction += new Baby().Cry;

调用

   cat.CatMiaoAction.Invoke();

 

1.4        事件和观察者模式

1.4.1  事件

由5.3.3.7委托实现猫叫示例和5.4.1.2事件实现猫叫示例可以看出,事件和委托的实现基本一样,事件只是多了一个event修饰的委托。实际上相当于委托的一个实例。

事件有一系列规则和约束用以保证程序的安全可控,事件只有 += 和 -= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。

1.4.1.1      标准事件

创建一个类,在类中声明一个事件

EventHandler(EventHandle是框架封装的委托,该委托有两个参数,一个是object事件源,一个EventArgs类,该类主要用于传递数据),

  public class School

    {

        public event EventHandler SchoolStateHandler;

        private string state;

        public string State {

            get { return state; }

            set

            {

                state = value;

                if (value == "OK")//当State改变时,触发绑定事件,并传入State参数

                    SchoolStateHandler?.Invoke(this, new XFEventArgs() { State = "OK" });

                else  if (value == "NO")

                        SchoolStateHandler?.Invoke(this, new XFEventArgs() { State = "NO" });

 

            } }

    }

同时创建一个继承于EventArgs的类,用来传递参数

public class XFEventArgs : EventArgs

    {

        public string State { get; set; }//用来传递State参数

    }

创建一些订阅事件的类,其中要有参数为(object obj,EventArgs e)的方法

public class  Teacher

    {//事件订阅类

        public void Teach(object obj,EventArgs e)

        {

 

            if (((XFEventArgs)e).State.Equals("OK"))

                Console.WriteLine("开始教学");

            else if(((XFEventArgs)e).State.Equals("NO"))

                Console.WriteLine("停止教学");

        }

    }

    public class Student

    {

        public void Study(object obj, EventArgs e)

        {

            if (((XFEventArgs)e).State.Equals("OK"))

                Console.WriteLine("开始学习");

            else if (((XFEventArgs)e).State.Equals("NO"))

                Console.WriteLine("回家休息");

        }

    }

创建类的实例,并订阅一些方法。

  School school = new School();

            school.SchoolStateHandler += new Teacher().Teach;

            school.SchoolStateHandler += new Student().Study;

当类中某些属性发生变化时触发事件。

school.State = "OK";

school.State = "NO";

 结果如下:

 

1.4.1.2      事件实现5.3.3.3中猫叫示例

1.4.1.2.1              在Cat类中声明一个事件

public event Action CatMiaoCationHandler;(委托是一个类,事件相当于是类的一个实例)

1.4.1.2.2              定义一个执行事件的方法

public void MiaoEvent()

        {

            Console.WriteLine($"{this.GetType().Name} MiaoEvent");

            this.CatMiaoCationHandler?.Invoke();//执行绑定方法

            Console.WriteLine($"{this.GetType().Name} MiaoEvent OVER");

        }

1.4.1.2.3              为事件绑定一些列方法

   cat.CatMiaoCationHandler += new Mouse().Run;

            cat.CatMiaoCationHandler += new Dog().Wang;

            cat.CatMiaoCationHandler += new Baby().Cry;

1.4.1.2.4              调用

   cat.MiaoEvent();

 

1.4.2  观察者模式

用观察者模式实现5.3.3.3中猫叫示例。

在Cat类中增加观察者集合和添加观察者方法,

通过调用观察者方法,逐一通知观察者。

  /// <summary>

        /// 添加一个观察者集合,用来存储观察者

        /// </summary>

        private List<IObject> list = new List<IObject>();

        /// <summary>

        /// 添加观察者

        /// </summary>

        /// <param name="observer"></param>

        public void AddObserver(IObject observer)

        {

            list.Add(observer);

        }

        /// <summary>

        /// 猫叫通知观察者

        /// </summary>

 

        public void MiaoObserver()

        {

            foreach(var item in list)

            {

                item.DoAction();

            }

            Console.WriteLine($"{this.GetType().Name} Miao Over");

        }

调用:

cat.AddObserver(new Mouse());

            cat.AddObserver(new Dog());

            cat.AddObserver(new Baby());

            cat.MiaoObserver();