Почему события не могут использоваться таким же образом в производных классах, как в базовом классе в C #? - PullRequest
28 голосов
/ 31 октября 2008

В следующем коде я хочу расширить поведение класса путем его создания / создания подкласса и использовать событие базового класса:

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 ]

42 голосов
/ 31 октября 2008

Другие объяснили, как обойти проблему, но не объяснили, почему она возникает.

Когда вы объявляете событие, подобное общедоступному полю, компилятор создает открытое событие и закрытое поле. В пределах одного и того же класса (или вложенных классов) вы можете получить непосредственно на поле, например, вызвать все обработчики. Из других классов вы видите только событие, которое позволяет только подписку и отписку.

34 голосов
/ 31 октября 2008

Стандартная практика здесь - иметь защищенный виртуальный метод OnSomeEvent в базовом классе, а затем вызывать этот метод в производных классах. Кроме того, по причинам многопоточности вы захотите сохранить ссылку на обработчик, прежде чем проверять значение null и вызывать его.

Для объяснения причины прочитайте ответ Джона Скита или спецификацию C # , которая описывает, как компилятор автоматически создает личное поле.

Вот один из возможных вариантов.

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();
    }
}

Редактировать: Обновлен код на основе Раздел 5.4 Руководства по проектированию платформы * и напоминаний другими.

5 голосов
/ 31 октября 2008

Тодд ответ правильный. Часто вы увидите, что это реализовано в .NET Framework как OnXXX(EventArgs) методы:

public class Foo
{
    public event EventHandler Click;

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

Я настоятельно рекомендую вам рассмотреть шаблон EventArgs<T> / EventHandler<T> , прежде чем вы обнаружите, что делаете все 100 * * * CustomEventHandler для поднятия событий.

3 голосов
/ 31 октября 2008

Причина, по которой исходный код не работает, заключается в том, что вам необходим доступ к делегату события, чтобы поднять его, а C # сохраняет этот делегат private.

События в C # публично представлены парой методов add_SomeEvent и remove_SomeEvent, поэтому вы можете подписаться на событие извне класса, но не вызывать его.

2 голосов
/ 31 октября 2008

Мой ответ будет таким: вам не нужно этого делать.

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
}
1 голос
/ 31 октября 2008

Оберните его с помощью защищенного виртуального метода 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 - все формы и веб-элементы управления следуют ему.

Не используйте префикс Raise ... - это не соответствует стандартам MS и может привести к путанице в других местах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...