【C#】委托和事件

一.委托的基本概念:

委托是一个类,用反编译代码看过去,声明为委托类型的变量实际上是继承自System.MulticastDelegate,这个类在实例化的时候可以接收一个方法名作为参数,接收的方法是有限制的,必须跟声明委托时候定义的返回值和参数列表相同。

(一) 委托的使用

委托的使用分为三步:

1.声明委托:委托声明,最前面是一个delegate关键字,后面是【 委托名】,在声明委托名的时候,要同时声明返回值(无返回值 加void)和参数列表,表示【委托名】这种类型的委托在实例化的时候,可以接收作为传入参数的方法的格式。那么反过来说,方法可以理解为委托的实例。

如下几个声明委托的例子:

 //声明一个泛型委托,NoReturnNoPara是一个委托类,这个委托类,可以接收所有返回值是void,参数为T的泛型方法。
delegate void NoReturnNoPara<T>(T t);
public delegate string WithReturnWithPara(out int x, ref int y);

2.实例化委托:实例化的过程就是把方法包装成对象的过程

例如:NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);

3.调用委托:

(1)利用委托类型里面的Invoke方法就可以实现同步调用委托

        例如:method.Invoke();

(2)利用委托类型里面的 BeginInvoke,可以实现委托的异步调用(调用这个委托的方法之后,不必等待其执行完毕,程序直接往下运行,):

//异步调用BeginInvoke,返回值是一个IAsyncResult,这个返回值并不是真正方法的返回值,而是记录了异
//步调用的状态信息
IAsyncResult result = method.BeginInvoke(null, null);
//如果方法有返回值并想要获取返回值,则需要调用EndInvoke方法,并将IAsyncResult这个值传入
int iRes = method.EndInvoke(result);

(二) C# 系统提供的两种委托

   1.Action类型的委托:C#里面定义Action委托用于抽象化那种没有返回值的方法

       Action类型的委托可以用于包装那些没有返回值,参数列表在0-16个传入参数的方法。(*Action 是一个类,实例化的时候可以将 无返回值,参数列表在0-16个传入参数的方法 当做参数传入,用其将他们封装)。

   2.Func类型的委托:C#里面Func类型的委托用于处理有参数有返回值的方法。

lamada表达式就是一种委托 中的匿名方法,如果想了解lamada表达式和委托的关系,请移步另外一篇介绍lamada表达式和Linq的博文。

(三) 关于多播委托:

多播委托:实例化一个委托实力变量后,它可以保存多个方法,可以增减;invoke的时候可以按顺序执行;

* += 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行

* -= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,如果没有要移除的方法,就跳过,不会异常

例如:

//实例化一个委托,这个委托可以加上多个方法。
NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);
                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();

                //method.BeginInvoke(null, null);//多播委托是不能异步的
                foreach (NoReturnNoPara item in method.GetInvocationList())
                {
                    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();

二.使用委托的意义:

在不熟悉委托的时候,那真的是要别扭死人了,因为调用方法直接用方法名调用就好了,何必还要定义一个委托变量去调用,这不是将简单问题复杂化么。

而事实上,委托在我们编程中有很重要的意义,如要有以下几点:

(一) .可以用委托实现异步多线程

           在一些简单的异步情境下,可以不必开Thread或者Task线程,而直接使用委托的BeginInvoke实现异步。

(二).利用由委托衍化而来的【事件】,可以实现观察者模式

          事件及事件实现观察者设计模式,将在后面详细写。

(三).实现程序的解耦。这点在框架设计的时候,尤为重要

框架设计 就是:完成固定/通用部分,把可变部分留出扩展点,支持自定义,留出的部分越灵活越好,可扩展的部分越多越好。所以在框架设计中,反射,委托,泛型这些能使程序变得灵活和扩展性强的技术会使用很多。(详情可以参考.Net的一些开源架构,比如Dapper,比如 MVC框架等等)。

三.事件

(一)什么是事件

在初学C#事件的时候,经常会有疑问,事件究竟是什么,看有文章介绍说,事件是一种特殊的委托,于是又有疑问,特殊的委托和委托有什么区别,特殊特殊在哪里等等。

1.事件的声明:

在定义事件的时候,只需要在前面加上一个关键字 event,就表示定义了一个事件。例如.net框架中的winform中就有很多事件,比如winform中的控件Button的Click事件的定义:

public event EventHandler Click;

其中关键字 event定义了 Click是一个事件,而EventHandler转去定义会发现他就是一个委托,详情如下:

public delegate void EventHandler(object sender, EventArgs e);//表示系统定义了一个 名为 EventHandler的委托,这个委托定义了一类方法,这类方法的传入参数是object 和 EventArgs ,返回值是void。

由上可知:定义事件 ,先要一个event关键字,后面是一个委托,再定义事件名。

用反编译程序查看事件,会发现编译器将定义为event的事件,包装成了一个委托实例,所以一个事件,其实就是一个委托的实例。那么事件究竟是一个怎样特殊的委托,,他的特殊之处在哪里呢?

2.事件是一种特殊的委托实例

事实上,所有用事件的地方,都可以用委托的替代在语法上不会出错,那么为什么还有出现事件这种东西,岂不是多余?

首先我们以Winform的Button控件中的Click事件为例,再来仔细的分析他一下,在我们点击了屏幕上的Button的时候,其实系统为我们做了大量的事情,比如收集鼠标点击的信息,要判断是不是点击在了Winform上,究竟是点击了哪个button,究竟是双击还是单击还是只是拖动等等,留给程序开发时候来做的仅仅是在单击Button之后需要执行的一部分逻辑,比如弹出个对话框写入数据库一类的事情,,所以在.net框架中,就需要把系统需要做的事情和需要由程序员自定义的这部分动作分离开,将程序员自定义的这部分操作设计成一个委托,由程序开发人员传入自己需要实现的方法过程,再由.net框架在执行过程中,在需要的时机调用程序开发人员写的具体执行逻辑。但是,这个委托的要求是要安全,当前这个winform上的Button控件的Click事件,只能在当前这个winform中在判断该Button被调用了之后才能被调用,而不能由外界随意随时调用,所以C#就设计了事件这种特殊的委托实例,在某个委托 前面用了event关键字变成一个事件之后,他就是内部安全的,它只能在当前的类中被调用,在类的外部只能为这个委托增加它能调用的方法,而真正的调用只能在定义这个委托的类的内部

于是事件给我们提供了这样一种编程思想,事件:可以把一堆可变的动作/行为封装出去,交给第三方来指定, 预定义一样,程序设计的时候,我们可以把程序分成两部分:     一部分是固定的,直接写死;还有不固定的地方,通过一个事件去开放接口,外部可以随意扩展动作。

在这个分离的过程中,程序就需要有这样两个角色出现:广播者和订阅者。

3.事件的基本使用:

事件的广播者其实就是事件的发布类,这个类在它内部声明一个事件发布出来(广播出来),并在恰当的时机调用这个事件,而这个事件具体的执行逻辑则是封装出去由事件的订阅者自定义。事件的订阅者订阅事件的过程就是将自定义的方法加到这个事件中去。

于是事件的基本使用就产生了如下过程:

1.在事件的广播的类中,先声明一个事件,再在适当的时机调用这个事件,,例如:

 

//定义一个委托
public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);

public class IPhone6 {    
//声明一个事件,其类型为  PriceChangedHandler 这个委托
public event PriceChangedHandler PriceChanged; 

public decimal Price  
{        
     get { return price; }         
     set {             
            if (price == value) return;
             decimal oldPrice = price;
             price = value;             // 如果调用列表不为空,则触发。
             if (PriceChanged != null)
                //广播者在适当的时机调用自己内部定义的事件
                 PriceChanged(oldPrice, price);         
         }
     }
}

2.在事件的订阅者的时候,将具体的执行逻辑封装为一个方法,加入到这个事件中,例如:下例中,要订阅 类IPhone6 中的PriceChanged事件

class Program 
{     
    static void Main() 
    {         
        IPhone6 iphone6 = new IPhone6() { Price = 5288 };         //                          
        iphone6.PriceChanged += iphone6_PriceChanged; // 订阅事件,调整价格(事件发生)         
        iphone6.Price = 3999;
        Console.ReadKey();
    }

   //封装具体执行逻辑
    static void iphone6_PriceChanged(decimal oldPrice, decimal price)
    {
         Console.WriteLine("年终大促销,iPhone 6 只卖 " + price + " 元, 原价 " + oldPrice + " 元,快来抢!");
     } 
}

再次强调,事件的好处:

但事件有一系列规则和约束用以保证程序的安全可控,事件只有 += 和 -= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。如果是委托,那么订阅者就可以使用 = 来对委托对象重新赋值(其它订阅者全部被取消订阅),甚至将其设置为null,甚至订阅者还可以直接调用委托,这些都是很危险的操作,广播者就失去了独享控制权。

事件保证了程序的安全性和健壮性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值