Подтвердить что ты не робот

С# Language Design: явная реализация интерфейса события

Небольшой вопрос о языке языка С#:))

Если бы у меня был такой интерфейс:

interface IFoo {
  int Value { get; set; }
}

Можно явно реализовать такой интерфейс, используя автоматически реализованные свойства С# 3.0:

sealed class Foo : IFoo {
  int IFoo.Value { get; set; }
}

Но если у меня было событие в интерфейсе:

interface IFoo {
  event EventHandler Event;
}

И попытка явно реализовать его с помощью полевого события:

sealed class Foo : IFoo {
  event EventHandler IFoo.Event;
}

Я получаю следующую ошибку компилятора:

error CS0071: An explicit interface implementation of an event must use event accessor syntax

Я думаю, что полевые события являются своего рода дуализмом для автоматически реализованных свойств.

Итак, мой вопрос: какова причина дизайна для такого ограничения?

4b9b3361

Ответ 1

Я предполагаю, что это может быть связано с тем, что вы не можете вызывать явную реализацию интерфейса от других членов класса:

public interface I
{
    void DoIt();
}

public class C : I
{
    public C()
    {
        DoIt(); // error CS0103: The name 'DoIt' does not exist in the current context
    }

    void I.DoIt() { }
}

Обратите внимание, что вы можете сначала вызвать метод, повысив его до интерфейса: ((I)this).DoIt();. Немного уродливый, но он работает.

Если события могут быть явно реализованы, как предположил ControlFlow (OP), то как бы вы их на самом деле поднять? Рассмотрим:

public interface I
{
    event EventHandler SomethingHappened;
}

public class C : I
{
    public void OnSomethingHappened()
    {
        // Same problem as above
        SomethingHappened(this, EventArgs.Empty);
    }

    event EventHandler I.SomethingHappened;
}

Здесь вы даже не можете воссоздать событие, предварительно повысив его до интерфейса, потому что события могут быть подняты только из класса реализации. Поэтому представляется, что имеет смысл требовать синтаксис доступа для явно реализованных событий.

Ответ 2

Интересный вопрос. Я несколько раз проговорил архив языковых заметок, и я обнаружил, что это решение было принято 13 октября 1999 года, но примечания не дают оснований для принятия решения.

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

Ответ 3

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

Например, это приведет к ошибке CS0071:

public delegate void MyEvent(object sender);

interface ITest
{
    event MyEvent Clicked;
}

class Test : Itest
{
    event MyEvent ITest.Clicked;  // CS0071
    public static void Main() { }
}

Правильный способ:

public delegate void MyEvent(object sender);

interface ITest
{
    event MyEvent Clicked;
}

class Test : Itest
{
    private MyEvent clicked;

    event MyEvent Itest.Clicked
    {
        add
        {
            clicked += value;
        }

        remove
        {
            clicked -= value;
        }
    }

    public static void Main() { }
}

см. Ошибка компилятора CS0071

Ответ 4

На самом деле это не было бы оригинальной мыслью.

Однако я подумал, что могу ответить на это:

"С головы до ног я не вижу никакой теоретической или практической причины, по которой мы не могли бы иметь явные события, подобные полям. Я также не вижу причин, по которым нам особенно нужно. Это, возможно, останется одним тайны неизвестного". -Eric Lippert


В главе 23 "Введение программиста на С#, второе издание" Эрик Гуннерсон писал:

"[I] f другой класс также хотел быть вызван, когда нажата кнопка, можно использовать оператор + =, например:

button.Click + = new Button.ClickHandler(OtherMethodToCall);

К сожалению, если другой класс не был осторожен, он мог бы сделать следующее:

button.Click = new Button.ClickHandler(OtherMethodToCall);

Это было бы плохо, так как это означало бы, что наш ButtonHandler будет отцеплен, и будет вызван только новый метод.

...

"Нужен какой-то способ защиты поля делегата, так что он доступен только с помощью + = и - =."


Он продолжает следующие несколько страниц комментировать, включая методы add() и remove() для реализации этого поведения; возможность напрямую записывать эти методы и последствия размещения хранилища для ненужных ссылок делегатов.

Я бы добавил больше, но я слишком уважаю автора, чтобы сделать это без его разрешения. Я рекомендую найти копию этой книги и рекомендовал бы что-нибудь Эрик Гуннерсон вообще (блог и т.д.)

В любом случае, я надеюсь, что это относится к теме, и если да, надеемся, что это светит на эту "тайну неизвестного"? (Я читал эту самую главу и искал Qaru для понимания логических соображений обработчика событий при создании пользовательских коллекций из пользовательских объектов). Я упоминаю об этом только потому, что не требую никаких конкретных полномочий по этому конкретному вопросу. Я просто ученик в поисках "просветления": -)