Effective C# Item22:使用事件定义外发接口

    事件为类型定义了外发接口,C#的事件是建立在委托的基础上的,委托为事件处理器提供了类型安全的函数签名。

    委托要比事件的使用范围广泛,我们可以把事件看做是一种经过了封装的委托,专门用于事件驱动模型。你可以在客户代码中直接调用委托来激发委托指向的函数,而事件不可以,你只能在服务端调用事件,在客户端调用事件是会引发编译错误的。我们来看下面的程序。

    public class EventTest
    {
        public delegate int Add(int value1, int value2);
        public event Add AddHandler;
        public Add AddDelegate;

        public void OutputAddResult(int value1, int value2)
        {
            AddHandler(value1, value2);
        }
    }

    public class ClientTest
    {
        private EventTest m_EventTest;

        public ClientTest()
        {
            m_EventTest = new EventTest();
            m_EventTest.AddHandler += new EventTest.Add(m_EventTest_AddHandler);
            m_EventTest.AddDelegate = AddDelegate;
            //the line below will cause compile error.
            //m_EventTest.AddHandler(1, 1);
             m_EventTest.AddDelegate(1, 1);
        }

        private int m_EventTest_AddHandler(int value1, int value2)
        {
            return value1 + value2;
        }

        private int AddDelegate(int value1, int value2)
        {
            return value1 + value2;
        }
    }


    上述代码也说明对于委托,你不但可以安排谁是它的调用函数,还可以直接调用它;而对于事件,你是不能直接调用的,只能通过某些操作触发。

    .NET针对Event类型的变量,定义了add和remove两个访问器,类似于普通属性中的get和set,通过add和remove,我们可以使用“+=” 或者 “-=”来注册事件或者解除事件。关于add和remove,是由编译器自动为我们生成的,在实际编写代码时,我们应该声明共有事件,然后让编译器来为我们创建add和remove访问器。

    在定义事件或者事件所在的类型中,是不需要知道潜在的客户调用方的信息的,即事件是只能够在服务器端调用,在客户端进行注册实现,但是服务器端是无需知道客户端的信息的,这两者是松耦合的。这里所说的服务器端和客户端,分别表示声明事件的类型和注册事件的类型。

    当我们的类型包含的事件比较多时,仍然采取为每一个事件定义个一个字段的方式,就变的不可取了,这时,我们可以定义一个事件的容器,在运行时,动态的创建事件对象。其中.NET框架内核在Windows控件子系统中包含有这方面的做法示例。

    我们可以查看下面的代码,使用了容器的方式来保存事件的具体信息。

public class Logger
{
  private static System.ComponentModel.EventHandlerList
    Handlers = new System.ComponentModel.EventHandlerList();

  static public void AddLogger(
    string system, AddMessageEventHandler ev )
  {
    Handlers[ system ] = ev;
  }

  static public void RemoveLogger( string system )
  {
    Handlers[ system ] = null;
  }

  static public void AddMsg ( string system, int priority,  string msg )
  {
    if ( ( system != null ) && ( system.Length > 0 ) )
    {
      AddMessageEventHandler l =
        Handlers[ system ] as AddMessageEventHandler;

      LoggerEventArgs args = new LoggerEventArgs(
        priority, msg );
      if ( l != null )
        l ( null, args );

      // The empty string means receive all messages:
      l = Handlers[ "" ] as AddMessageEventHandler;
      if ( l != null )
        l( null, args );
    }
  }
}


    上述代码会在EventHandlerList集合中存储各个事件处理器,当客户代码关联到一个特定的子系统(或者说Key值)时,新的事件对象就会被创建。对于同一个Key值,其后的请求会返回相同的事件对象,因为容器是一个静态容器。如果我们的类型在其接口中包含有大量的时间,那么我们就应该采用这种事件容易的方式,当客户代码真正关联事件处理器时,我们才会创建事件成员。

    总结:我们使用事件来定义类型中的外发接口,任意数量的客户对象都可以将自己的处理器注册到事件上,然后处理它们,这些客户对象不需要在编译时存在,事件也不必非有订阅者才可以正常工作。在C#中使用事件可以对发送者和可能的通知接收者进行解耦,发送者完全可以独立于接收者进行开发。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值