C#学习笔记(三)—–C#高级特性中的委托与事件:关于事件

C#高级特性中的委托与事件:关于事件

本笔记写在C#学习笔记(三)—–C#高级特性中的委托与事件(下)章节后面,对前面的章节做一个补充。

我觉得初学者可以通过张子阳的博客来学习委托和事件也是比较好的一个选择,里面关于委托的例子写的比较好,步步深入的方法也让初学者有一个比较舒服的学习过程,他有一本.NET之美的书籍,不妨买来看看,我当时就是看他的博客,学到了一些东西,为了表示对作者的尊重,买了一本他的书,知识不白教,这样也可以推动作者将更多好的知识写出来供我们学习。

泛型和委托

上一节指出,为事件定义类型的规范是使用EventHandler委托类型。理论上任何委托类型都可以使用,但根据约定,第一个参数sender是object类型,第二个参数e是从System.EventArgs派生的一个类型。对于C# 1.0中的委托来说,一个较大的麻烦在于,一旦事件处理程序的参数发生改变,就不得不声明一个新的委托类型。每次从System.EventArgs派生(这是相当常见的一个情形),都要声明一个新的委托数据类型来使用新的EventArgs派生类型。例如,为了使用代码清单13-14的事件通知代码中的TemperatureArgs,必须声明委托类型TemperatureChangeHandler,它以TemperatureArgs作为参数,如下代码所示:

public class Thermostat
{
public class TemperatureArgs: System.EventArgs
{
public TemperatureArgs( float newTemperature )
{
NewTemperature = newTemperature;
}
public float NewTemperature
{
get{return _newTemperature;}
set{_newTemperature = value;}
}
private float _newTemperature;
}
public delegate void TemperatureChangeHandler(
object sender,TemperatureArgs newTemperature);
public event TemperatureChangeHandler
OnTemperatureChange;
public float CurrentTemperature
{
...
}
private float _CurrentTemperature;
}

虽然通常应优先使用EventHandler,而非创建TemperatureChangeHandler这样的自定义委托类型,但后者也是有一些优点的。具体地说,如果使用自定义类型,就可以使用事件特有的参数名。例如代码清单13-15调用委托来引发事件时,第二个参数名是newTemperature,而不是一个让人摸不着头脑的e。
使用自定义委托类型的另一个原因涉及C# 2.0之前定义的CLR API。因为在框架中相当大一部分都是比较常见的类型,那么在处理来自于CLR API的事件时,就不难遇到具体的委托类型而不是事件的泛型形式。但无论如何,在C# 2.0和之后使用事件的大多数情形中,都没必要声明自定义委托数据类型。
规范是要为事件处理程序使用System.EventHandler而非手动创建新的委托类型,除非自定义类型的参数名能提供有意义的说明。

  • 事件内部的机制:事件限制外部类只能通过“+=”操作符向发布者添加订阅方法,并用“-=”操作符取消订阅,除此之外的任何事情都不允许做。此外,它们还禁止除包容类之外的其他任何类调用事件。为了达到上述目的,C#编译器会获取带有event修饰符的public委托变量,并将委托声明为private。此外,它还添加了两个方法和两个特殊的事件块。从本质上说,event关键字是编译器用于生成恰当封装逻辑的一个C#快捷方式。来看看代码清单1的事件声明示例。

代码清单1:

public class Thermostat
{
public event EventHandler<TemperatureArgs> OnTemperatureChange
...
}

C#编译器一旦遇到event关键字,就会生成代码清单2中那样的与上述代码等价的CIL代码。
代码清单2

public class Thermostat
{
// ...
// Declaring the delegate field to save the
// list of subscribers.
private EventHandler<TemperatureArgs> _OnTemperatureChange;
public void add_OnTemperatureChange(
EventHandler<TemperatureArgs> handler)
{
  System.Delegate.Combine(_OnTemperatureChange,handler);
}
public void remove_OnTemperatureChange(
EventHandler<TemperatureArgs> handler)
{
  System.Delegate.Remove(_OnTemperatureChange,handler);
}
public event EventHandler<TemperatureArgs> OnTemperatureChange
{
add
{
 _OnTemperatureChanged= (EventHandler<TempratureEventArgs>)add_OnTemperatureChange(value)
}
remove
{
 _OnTemperatureChanged= (EventHandler<TempratureEventArgs>)remove_OnTemperatureChange(value)
}
}
}

换言之,代码清单1的代码会造成编译器自动对代码进行扩展,并生成大致代码清单2所示的代码。(“大致”一词不可或缺,因为为了简化问题,和线程同步有关的细节从代码清单中拿掉了。)代码清单中的代码我做了一些修改便于读者使用实例直接得出结果。
C#编译器首先获取原始事件定义,并在原位置定义一个私有委托变量。结果,从任何外部类中都无法使用该委托——即使是从它的派生类中。接着,C#编译器定义了两个方法:add_OnTemperatureChange()和remove_OnTemperatureChange()。其中,OnTemperatureChange后缀是从原始事件名称中截取的。这两个方法分别负责实现“+=”和“-=”赋值操作符。如代码清单2所示,这两个方法是使用本章前面讨论的静态方法System.Delegate.Combine()和System.Delegate.Remove()来实现的。传给方法的第一个参数是私有的EventHandler<TemperatureArgs>委托实例OnTemperatureChange。add方法对应的是委托中的Combine,remove方法对应的是委托中的Remove。
在从event关键字生成的代码中,或许最奇怪的就是最后一部分。其语法与属性的取值和赋值方法非常相似,只是方法名变成了add和remove。其中,add块负责处理“+=”操作符,将调用传给add_OnTemperatureChange()。类似地,remove块处理“-=”操作符,将调用传给remove_OnTemperatureChange()。
必须重视这段代码与属性代码的相似性。本书前面讲过,C#在实现一个属性时,会创建get_<propertyname>set_<propertyname>,然后将对get和set块的调用传给这些方法。顺便的,还会创建一个后备字段。显然,事件的语法与此非常相似。
另外要注意,在最终的CIL代码中,仍然保留了event关键字。换言之,事件是CIL代码能够显式识别的一样东西,并非只是一个C#构造。在CIL代码中保留一个等价的event关键字之后,所有语言和编辑器都能将事件识别为一个特殊的类成员,并正确地处理它。
- 自定义事件的实现:编译器为+=和-=生成的代码是可以自定义的。例如,假定改变OnTemperatureChange委托的作用域,使它成为protected而不是private。这样一来,从Thermostat派生的类也能直接访问委托,而无需受到和外部类一样的限制。为此,C#允许使用”属性“语法。换言之,C#允许添加自定义的add和remove块,为事件封装的各个组成部分提供你自己的实现。代码清单3展示了一个例子。

 class Thermostat
    {
        public class TempratureEventArgs : System.EventArgs
        {
            public float CurrentTemprature { get; set; }

            public TempratureEventArgs(float newTemprature)
            {
                CurrentTemprature = newTemprature;
            }
        }      
        protected EventHandler<TempratureEventArgs> _OnTemperatureChanged;
        public event EventHandler<TempratureEventArgs> OnTemperatureChange
        {
            add
            {
              _OnTemperatureChanged= (EventHandler<TempratureEventArgs>)System.Delegate.Combine(value, _OnTemperatureChanged);               
            }
            remove
            {
                _OnTemperatureChanged = (EventHandler<TempratureEventArgs>)System.Delegate.Remove(_OnTemperatureChanged, value);
            }
        }       
        private float _temperature;       
        public float CurrentTemperature
        {
            get
            {
                return _temperature;
            }
            set { _temperature = value;
               _OnTemperatureChanged(this,new TempratureEventArgs(CurrentTemperature));
            }
        }
    }

在这个例子中,存储每个订阅者的委托_OnTemperatureChange变成了protected。除此之外,在add块的实现中,我们交换了两个委托存储的位置,使添加到链中的最后一个委托是接收通知的第一个委托。

本节描述了事件,值得一提的是,通常,方法引用是唯一可以在事件上下文的外部用到委托变量的情况。换句话说,由于事件提供了额外的封装性,而且允许在必要时对实现进行自定义,所以最佳做法就是始终为Observer模式使用事件。在这里需要特别提醒需要注意的一点是,在类中,如果我们定义了事件,那么最终编译器会在后台生成一个私有的委托字段,比如,我们在类中定义一个public event EventHandler DoSomeThing;后台会生成一个private EventHandler DoSomeThing;我们在类中可以编写:DoSomething(someobject,someEventArgs),这个表达式告诉委托链上面的方法,要开始执行方法了,但是,注意!:这个不是用事件发布的通知,这个是用委托发布的通知!
可能需要一段时间的练习,才能脱离示例代码,熟练地进行事件的编程。然而,事件编程正是以后要讲述的异步、多线程编程的重要基础。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值