为什么事件不能在派生类中以与C#中的基类相同的方式使用?

时间:2008-10-31 14:41:17

标签: c# events

在下面的代码中,我想通过派生/子类化来扩展类的行为,并使用基类的事件:

public class A
{
    public event EventHandler SomeEvent;

    public void someMethod()
    {
        if(SomeEvent != null) SomeEvent(this, someArgs);
    }
}

public class B : A
{
    public void someOtherMethod()
    {
        if(SomeEvent != null) SomeEvent(this, someArgs); // << why is this not possible?
//Error: The event 'SomeEvent' can only appear on the left hand side of += or -= 
//(except when used from within the type 'A')
    }
}

为什么不可能?

这种情况的常见解决方案是什么?

6 个答案:

答案 0 :(得分:42)

其他人已经解释了如何解决问题,但不是为什么会出现问题。

当您声明类似公共字段的事件时,编译器会创建一个公共事件和一个私有字段。在同一个类(或嵌套类)中,您可以直接进入该字段,例如调用所有处理程序。从其他类中,您只能看到该事件,该事件仅允许订阅和取消订阅。

答案 1 :(得分:34)

这里的标准做法是在基类上有一个受保护的虚方法OnSomeEvent,然后在派生类中调用该方法。此外,出于线程原因,您需要在检查null并调用它之前保留对处理程序的引用。

有关阅读Jon Skeet's答案或C# specification的原因的说明,其中描述了编译器如何自动创建私有字段。

这是一个可能的解决方法。

public class A
{
    public event EventHandler SomeEvent;

    public void someMethod()
    {
        OnSomeEvent();
    }

    protected void OnSomeEvent()
    {
        EventHandler handler = SomeEvent;
        if(handler != null)
            handler(this, someArgs);
    }
}

public class B : A
{
    public void someOtherMethod()
    {
        OnSomeEvent();
    }
}

修改:其他人根据Framework Design Guidelines section 5.4reminders更新了代码。

答案 2 :(得分:5)

托德的回答是正确的。通常,您会看到这在整个.NET框架中以OnXXX(EventArgs)方法实现:

public class Foo
{
    public event EventHandler Click;

    protected virtual void OnClick(EventArgs e)
    {
        var click = Click;
        if (click != null)
            click(this, e);
    }
}

我强烈建议您在发现自己以CustomEventArgs / CustomEventHandler提出各种活动之前考虑EventArgs<T>/EventHandler<T> pattern

答案 3 :(得分:3)

原始代码不起作用的原因是因为您需要访问事件的委托才能引发它,而C#会保留此委托private

C#中的事件由一对方法add_SomeEventremove_SomeEvent公开表示,这就是为什么你可以从课外订阅一个事件,但不提高它。

答案 4 :(得分:2)

我的回答是你不应该这样做。

C#很好地执行只有声明/发布事件的类型才会触发/提升它。 如果基类受信任的派生具有提升其事件的能力,则创建者将公开受保护的方法来执行此操作。如果它们不存在,那么它可能不应该这样做。

我设想的例子,如果允许派生类型在他们的祖先中引发事件,世界会有多么不同。注意:这是有效的C#代码..(还是..)

public class GoodVigilante
{
  public event EventHandler LaunchMissiles;

  public void Evaluate()
  {
    Action a = DetermineCourseOfAction(); // method that evaluates every possible
// non-violent solution before resorting to 'Unleashing the fury'

    if (null != a) 
    { a.Do(); }
    else
    {  if (null != LaunchMissiles) LaunchMissiles(this, EventArgs.Empty); }
  }

  virtual protected string WhatsTheTime()
  {  return DateTime.Now.ToString();  }
  ....   
}
public class TriggerHappy : GoodVigilante
{
  protected override string WhatsTheTime()
  {
    if (null != LaunchMissiles) LaunchMissiles(this, EventArgs.Empty);
  }

}

// client code
GoodVigilante a = new GoodVigilante();
a.LaunchMissiles += new EventHandler(FireAway);
GoodVigilante b = new TriggerHappy();             // rogue/imposter
b.LaunchMissiles += new EventHandler(FireAway);

private void FireAway(object sender, EventArgs e)
{
  // nuke 'em
}

答案 5 :(得分:1)

用受保护的虚拟On ...方法包裹它:

public class BaseClass
{
    public event EventHandler<MyArgs> SomeEvent;

    protected virtual void OnSomeEvent()
    {
        if(SomeEvent!= null)
            SomeEvent(this, new MyArgs(...) );
    }
}

然后在派生类

中覆盖它
public class DerivedClass : BaseClass
{
    protected override void OnSomeEvent()
    {
        //do something

        base.OnSomeEvent();
    }
}

您将遍布.Net设置此模式 - 所有表单和Web控件都遵循它。

不要使用前缀Raise ...... - 这与MS的标准不一致,可能会引起其他地方的混淆。