В С# почему я не могу передать ссылку на другой класс "EventHandler" и как я могу обойти это?

Если у меня есть ClassA, у которого есть публичное событие, SomeEvent и ClassC, у которого есть метод, addListener, который принимает ссылку EventHandler, почему у ClassB нет строки, которая говорит c.addListener(ref a.SomeEvent)? Если я попробую, я получу ошибку компилятора, которая говорит: "Событие" ClassA.SomeEvent "может появляться только в левой части + = или - = (кроме случаев, когда оно используется в классе" ClassA ").

Почему это ограничение существует? И как я могу обойти его, находясь достаточно близко к моей структуре?

Я новичок С#; любая помощь будет оценена по достоинству. Спасибо!

class ClassA {
    public event EventHandler SomeEvent;
}

ClassB{
    public ClassB() {

        ClassA a = new ClassA();
        ClassC c = new ClassC();
        c.addListener(ref a.SomeEvent);  //Compile error
    }
}

class ClassC {
    public void addListener(ref EventHandler handler) {
        handler += onEvent;
    }

    private void onEvent(object sender, EventArgs e) {
        //do stuff
    }

}

Ответ 1

Ключевое слово события создает аксессуар для частного объекта делегата. То же самое, что и свойство, оно ограничивает доступ к частному полю. Сбой кода вашего кода с аналогичной ошибкой при использовании свойства вместо события:

  class ClassA {
    public int Property { get; set; }
  }
  class ClassB {
    public ClassB() {
      ClassA a = new ClassA();
      ClassC c = new ClassC();
      c.setValue(ref a.Property);   // CS0206
    }
  }
  class ClassC {
    public void setValue(ref int value) {
      value = 42;
    }
  }

Теперь легче видеть, компилятор не может гарантировать, что метод setValue() использует свойство setter. Также он не мог знать, что аргумент "значение" является свойством с установщиком или простым полем.

Это менее понятно для события, потому что на работе так много синтаксического сахара. Это объявление

public event EventHandler SomeEvent;

действительно генерирует этот код:

private EventHandler _SomeEvent;
public event SomeEvent {
  add    { _SomeEvent += new EventHandler(value); }
  remove { _SomeEvent -= new EventHandler(value); }
}

Аксессоры добавления и удаления эквивалентны get и set accessors свойства, они не позволяют коду взаимодействовать с частным полем _SomeEvent. По соглашению, add accessor вызывается, когда вы используете + =, remove вызывается с помощью = =. Сравните это с предыдущим примером, который я дал для свойства. Такая же проблема, вы не можете использовать ключевое слово ref, и ClassC.addListener() не имеет никакого способа узнать, что обработчик на самом деле является событием вместо объекта-делегата. Если вместо этого компилятор передаст _SomeEvent, точка использования аксессуаров будет потеряна.

Вы можете реструктурировать код для решения этой проблемы:

  class ClassC {
    public EventHandler getListener() {
      return new EventHandler(onEvent);
    }
    private void onEvent(object sender, EventArgs e) { }
  }
...
      a.SomeEvent += c.getListener();

Последнее замечание: симметрия между событием и свойством немного потеряна, компилятор С# автоматически генерирует add/remove accessors, если вы не пишете их явно. Он не делает этого для собственности. Это сделало бы автоматические свойства намного проще:

property int Property;

Но это потребовало бы добавления нового ключевого слова на язык, что-то, что команда С# действительно не любит. Другие языки, такие как VB.NET и С++/CLI, имеют это ключевое слово.

Ответ 2

Вне класса у вас только есть доступ к add и remove accessors - это точка события, в котором вы не можете видеть других подписчиков и не изменять их (например, установив событие в null). Лучше было бы нормально справляться с этим событием и приводить к каким-либо последствиям.

Представьте, что вы можете делать то, что вы предлагаете. Например, предположим, что вы подписываетесь на нажатие кнопки, а какой-то другой код использует эту информацию, чтобы привлечь вас к событию "tick" - вы не собираетесь работать, как ожидалось, = ошибка.

Чтобы сделать это; событие не a EventHandler, так же как свойство не a int - свойство event/определяет методы доступа.

Повторите свой сценарий, либо сделайте OnEvent общедоступным, либо используйте a.SomeEvent += c.OnEvent;, либо используйте аналогичный метод и используйте anon-метод:

a.SomeEvent += delegate { c.DoSomethingCool(); };

Ответ 3

Как я могу обойти его, находясь достаточно близко к моей структуре?

Используйте a.SomeEvent += handler вместо этого.

Почему это ограничение существует?

См. ответ Марка Гравелла.