C # Language Design: явная реализация интерфейса события - PullRequest
33 голосов
/ 15 февраля 2010

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

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

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

Можно явно реализовать такой интерфейс, используя автоматически реализованные свойства C # 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

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

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

Ответы [ 4 ]

30 голосов
/ 15 февраля 2010

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

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

26 голосов
/ 07 февраля 2012

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

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

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

18 голосов
/ 15 февраля 2010

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

Например, это вызовет ошибку 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

1 голос
/ 06 мая 2010

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

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

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


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

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

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

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

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

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

...

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


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

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

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

...