探索.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
posted @ 2012-09-27 10:53  Serviceboy  阅读(1567)  评论(3编辑  收藏  举报