zoukankan      html  css  js  c++  java
  • 探索.NET中事件机制(续)——虚事件和事件重写问题,微软的Bug?!

    上一篇写到了微软经典的事件处理机制是通过EventHandlerList方式进行事件保存并且处理的。本节讲一个奇怪的问题——虚事件和事件重写问题(具体提问在:http://social.msdn.microsoft.com/Forums/zh-CN/csharpgeneral/thread/14400510-44da-4e95-95fa-fa584edc1917)请诸位看下文再说——

    现在假设有这样一个类:

    [C#]

    namespace CSharp
    {
        public class A
        {
            public virtual event Action MyEvent = null;
    
            public void RaiseMyEvent()
            {
                MyEvent();
            }
        }
    
        public class B : A
        {
            public override event Action MyEvent;
            static void Main(string[] args)
            {
                A a = new B();
                a.MyEvent += new Action(() => { Console.WriteLine("B"); });
                a.RaiseMyEvent();
            }
        }
    }

    [VB.NET]

    Namespace CSharp
        Public Class A
            Public Overridable Event MyEvent As Action = Nothing
    
            Public Sub RaiseMyEvent()
                RaiseEvent MyEvent()
            End Sub
        End Class
    
        Public Class B
            Inherits A
            Public Overrides Event MyEvent As Action
            Private Shared Sub Main(args As String())
                Dim a As A = New B()
                AddHandler a.MyEvent, New Action(Function() 
                Console.WriteLine("B")
    
    End Function)
                a.RaiseMyEvent()
            End Sub
        End Class
    End Namespace

    照例而言,B继承子A,并且重写了A的事件。Main入口函数用A的实体指向B,调用了来自于A的RaiseMyEvent方法触发了被重写的事件MyEvent(明显是触发了B那个被重写的事件!),但是这个程序运行的结果令人大失所望——因为竟然抛出了“空指针异常”!Why?

    我给出的一个外国人怀疑是VS的Bug,我极度怀疑!因为默认情况下的event定义相当于是一个“私有变量”,如果把上面的代码展开之后类似如下形式:

    [C#]

    namespace CSharp
    {
        public class A
        {
            private Action act = null;
            public virtual event Action MyEvent
            {
                add
                {
                    act = value;
                }
                remove
                {
                    act -= value;
                }
            }
    
            public void RaiseMyEvent()
            {
                act();
            }
        }
    
        public class B : A
        {
            private Action act = null;
    
            public override event Action MyEvent
            {
                add
                {
                    act += value;
                }
                remove
                {
                    act -= value;
                }
            }
    
            static void Main(string[] args)
            {
                A a = new B();
                a.MyEvent += new Action(() => { Console.WriteLine("B"); });
                a.RaiseMyEvent();
            }
        }
    }

    [VB.NET]

    Namespace CSharp
        Public Class A
            Private act As Action = Nothing
            Public Overridable Custom Event MyEvent As Action
                AddHandler(ByVal value As Action)
                    act = value
                End AddHandler
                RemoveHandler(ByVal value As Action)
                    act -= value
                End RemoveHandler
            End Event
    
            Public Sub RaiseMyEvent()
                act()
            End Sub
        End Class
    
        Public Class B
            Inherits A
            Private act As Action = Nothing
    
            Public Overrides Custom Event MyEvent As Action
                AddHandler(ByVal value As Action)
                    act += value
                End AddHandler
                RemoveHandler(ByVal value As Action)
                    act -= value
                End RemoveHandler
            End Event
    
            Private Shared Sub Main(args As String())
                Dim a As A = New B()
                AddHandler a.MyEvent, New Action(Function() 
                Console.WriteLine("B")
    
    End Function)
                a.RaiseMyEvent()
            End Sub
        End Class
    End Namespace

    这样理解就比单纯的event写法理解要容易的多:

    1)A = New B:实例化B(注意C#中实例化子类先要实例化由父类继承下的全部变量,因此act=null

    2)然后为a实体的MyEvent增加了一个事件方法(注意,其实是b实体的,这点在B类中通过断点调试便可窥之一般;因此“重写”这一点没有任何的Bug)。

    3)此时由于B自身也有一个私有的act,因此为重写中的B类增加事件方法的本质是给B类私有变量act增加一个事件方法对象,RaiseEvent其实是调用B类的MyEvent,而B类中的MyEvent早就实例化了!问题在这里——应该调用B的MyEvent但是实际上却调用了A的MyEvent!

    那么解决方案是什么呢?

    【1】首先考虑到的是:既然是private变量导致,因此我们可以借助重写调用各自内部事件的方法,通过方法的重写调用内部私有的委托。

    [C#]

    namespace CSharp
    {
        public class A
        {
            public virtual event Action MyEvent = null;
    
            public virtual void RaiseMyEvent()
            {
                MyEvent();
            }
        }
    
        public class B : A
        {
            public override event Action MyEvent;
            public override void RaiseMyEvent()
            {
                this.MyEvent();
            }
            static void Main(string[] args)
            {
                A a = new B();
                a.MyEvent += new Action(() => { Console.WriteLine("B"); });
                a.RaiseMyEvent();
            }
        }
    }

    [VB.NET]

    Namespace CSharp
        Public Class A
            Public Overridable Event MyEvent As Action = Nothing
    
            Public Overridable Sub RaiseMyEvent()
                RaiseEvent MyEvent()
            End Sub
        End Class
    
        Public Class B
            Inherits A
            Public Overrides Event MyEvent As Action
            Public Overrides Sub RaiseMyEvent()
                Me.MyEvent()
            End Sub
            Private Shared Sub Main(args As String())
                Dim a As A = New B()
                AddHandler a.MyEvent, New Action(Function() 
                Console.WriteLine("B")
    
    End Function)
                a.RaiseMyEvent()
            End Sub
        End Class
    End Namespace

    这个例子实际上是间接证明事件其实是可以被重写的(实际答案也是如此!)——因为A的对象指向了B的实体,B重写了A的事件,因此调用a.MyEvent其实是等同于调用B的那个重写的MyEvent,自然输出B。

    不过另外一个解决方法就是人为重写事件(展开成add……remove的形式),这样的好处在于变量可以不是private的了:

    [C#]

    namespace CSharp
    {
        public class A
        {
            protected List<Action> actions = new List<Action>();
    
            public virtual event Action MyEventA
            {
                add
                {
                    actions.Add(value);
                }
                remove
                {
                    actions.Remove(value);
                }
            }
    
            public void Say()
            {
                foreach (var item in actions)
                {
                    item();
                }
            }
        }
    
        public class B : A
        {
            public override event Action MyEventA
            {
                add
                {
                    actions.Add(value);
                }
                remove
                {
                    actions.Remove(value);
                }
            }
    
            static void Main(string[] args)
            {
                A a = new A();
                a.MyEventA += new Action(() => { Console.WriteLine("A"); });
                B b = new B();
                b.MyEventA += new Action(() => { Console.WriteLine("B"); });
                a = b;
                a.Say();
            }
        }
    }

    [VB.NET]

    Namespace CSharp
        Public Class A
            Protected actions As New List(Of Action)()
    
            Public Overridable Custom Event MyEventA As Action
                AddHandler(ByVal value As Action)
                    actions.Add(value)
                End AddHandler
                RemoveHandler(ByVal value As Action)
                    actions.Remove(value)
                End RemoveHandler
            End Event
    
            Public Sub Say()
                For Each item As var In actions
                    item()
                Next
            End Sub
        End Class
    
        Public Class B
            Inherits A
            Public Overrides Custom Event MyEventA As Action
                AddHandler(ByVal value As Action)
                    actions.Add(value)
                End AddHandler
                RemoveHandler(ByVal value As Action)
                    actions.Remove(value)
                End RemoveHandler
            End Event
    
            Private Shared Sub Main(args As String())
                Dim a As New A()
                AddHandler a.MyEventA, New Action(Function() 
                Console.WriteLine("A")
    
    End Function)
                Dim b As New B()
                AddHandler b.MyEventA, New Action(Function() 
                Console.WriteLine("B")
    
    End Function)
                a = b
                a.Say()
            End Sub
        End Class
    End Namespace
  • 相关阅读:
    一个简单例子:贫血模型or领域模型
    eclipse从数据库逆向生成Hibernate实体类
    Hibernate unsaved-value 属性
    webservice和restful的区别
    Web Service 的工作原理
    Hibernate3的DetachedCriteria支持
    hibernate criteria中Restrictions的用法
    Google Gson 使用简介
    struts2 访问国际化资源 <s:text>作为属性
    EL表达式从request和session中取值
  • 原文地址:https://www.cnblogs.com/ServiceboyNew/p/2705237.html
Copyright © 2011-2022 走看看