事件也是方法。

定义一个事件成员意味着类型具有三种能力:
    *类型的静态方法/实例方法可以订阅类型事件
    *类型的静态方法/实例方法可以注销类型事件
    *事件发生时通知已订阅事件的方法

.NET2.0的事件仍然是基于Win32的,只不过使用了Observer模式来实现,同时建立在Delegate机制之上。
事件的设计步骤如下(基本上是Observer的实现步骤):

10.1    设计一个对外提供事件的类型

1.定义EventArgs或子类,用于存放附加信息:
    定义一个类,继承于EventArgs,以EventArgs结束,包含一组私有字段以及相应的只读公共属性。
    public class NewMailEventArgs : EventArgs
    {
        
private string from;

        
public string From
        {
            
get { return from; }
        }
    }
   
    这里,EventArgs基类在FCL中是这个样子的:
    [Serializable]
    [ComVisible(
true)]
    
public class EventArgs
    {
        
// Summary:
        
//     表示没有事件数据的事件。
        public static readonly EventArgs Empty;

        
public EventArgs();
    }

    大多数事件没有附加数据,那么就不用定义任何私有字段和属性,直接使用EventArgs基类作为参数。

2.定义事件成员:
CLR笔记:10.事件    class MailManager
    }

    这条语句等价于:
CLR笔记:10.事件        public delegate void EventHandler<TVEventArgs>(Object sender, TVEventArgs e) where TVEventArgs: NewMailEventArgs;

    所以方法原型相应为 void MethodName(Object sender, NewMailEventArgs e)

    这里,第一个参数sender类型是Object,因为要兼容所有类型,所以提供一个最广泛的基类型。
            第二个参数名始终是e,而且派生于EventArgs,保持了对Observer模式的一致性,所有人(包括VS2005)都会调用这个e
            事件方法要求都为void,即不允许有回调值,从而事件链易于操作。

3.定义引发事件的方法——负责通知订阅事件的对象:
    这是一个protected的虚方法,并接受EventArgs或其子类的参数。
    这个虚方法可以由派生类重写,以添加新的功能;不重写也可以,因为基本上已经可以使用了
CLR笔记:10.事件    class MailManager
    }

    这里,使用临时变量temp,是为了防止可能存在的线程同步问题。

4.定义一个激发事件的方法

将输入转换成EventArgs或其子类的对象,然后激发事件
CLR笔记:10.事件    internal class MailManager
    }


10.3    设计订阅者的类,使用事件
    在ctor中订阅事件,绑定FaxMsg回调方法,在Unregister方法中注销事件
    提供回调方法FaxMsg,当事件激发时自动调用
CLR笔记:10.事件    internal sealed class Fax
    }
    
注意:使用+=和-=操作符,而不能显示使用add/remove方法
        事件注销的意义:只要有一个对象还有一个方法仍然订阅事件,该对象就不会被垃圾收集
            IDispose接口的Dispose方法,注销所有事件。
        FaxMsg方法的sender参数为MailMessager对象,可以使用sender访问MailMessager的对象成员,

补充:在Main函数中实现:
   }


10.2    事件机制
对于public event EventHandler<NewMailEventArgs> NewMail;
    C#编译时,相应为
CLR笔记:10.事件            //一个初始化为null的私有委托字段:    
CLR笔记:10.事件
            private EventHandler<NewMailEventArgs> NewMail = null;
CLR笔记:10.事件
CLR笔记:10.事件            
//一个订阅事件的公共方法:
CLR笔记:10.事件
            [MethodImpl(MethodImplOptions.Synchronized)]
CLR笔记:10.事件            
public void add_NewMail(EventHandler<NewMailEventArgs> value)
            }


注:    在IL中也是3个成员:一个私有字段,两个公有方法
          如果将event声明为protected,则两个方法也相应为protected
          event也可以是static或virtual,则两个方法也相应为static或virtual


10.4    事件与线程安全
在上面的实例中,System.Runtime.CompilerServices命名空间下,自定义属性[MethodImpl(MethodImplOptions.Synchronized)]保证了事件的线程同步。
但是这样的同步会有问题。
    对于实例事件,CLR使用自身对象作为线程同步锁;
    对于静态事件,CLR使用类型对象作为线程同步锁。
但是线程同步指导方针指出,方法永远不要在对象本身或类型对象上加锁,否则这个锁对外公开,会导致其它线程死锁

没有好的办法保证值类型的实例事件成员是线程安全的,因为C#不会为其add/remove生成[MethodImpl(MethodImplOptions.Synchronized)]
值类型的静态事件成员肯定是线程安全的。

10.5    显示控制事件的订阅与注销
即显示的实现add和remove访问器方法:
    建立一个临时委托变量m_NewMail与相应的属性,代替原先的事件成员NewMail,
    新建一个作为线程同步锁的私有实例字段m_eventLock
主要改动如下:
CLR笔记:10.事件    class MailManager
    }

注意,C#不能分辨add/remove方法是由编译器自动创建的,还是程序员显示实现的,所以仍可以使用+=和-=这两个操作符处理事件。

10.6    多事件模型
System.Windows.Forms.Control类型有70多个事件,不可能用上述方法实现,会造成未使用事件对内存的浪费。
解决办法:使用注册工厂,建立事件池。具体见设计模式。




相关文章:

  • 2022-02-12
  • 2022-12-23
  • 2021-10-11
  • 2021-07-27
  • 2021-08-30
  • 2022-12-23
  • 2021-08-17
  • 2021-05-16
猜你喜欢
  • 2021-07-15
  • 2022-02-22
  • 2021-05-17
  • 2022-12-23
  • 2022-12-23
  • 2021-08-03
  • 2022-12-23
相关资源
相似解决方案