Возможно ли подписаться на publi c EventHandler в классе, созданном приватным конструктором, где возвращается интерфейс? - PullRequest
0 голосов
/ 21 марта 2020

У меня есть класс publi c с определением делегата publi c EventHandler, но с закрытым конструктором. Класс создается с использованием метода stati c, и я не знаю, можно ли или как на него подписаться, не раскрывая конструктор publi c.

В приведенном ниже примере показано, как ISomeService возвращается из стати c метод UsingHttpClient. Таким образом, создается экземпляр класса, но поскольку возвращается интерфейс, а не экземпляр класса, для вызова someInstance.SomeEvent += OnSomeEvent не существует «дескриптора».

public class SomeService
{
    public EventHandler<bool> SomeEvent;
    private readonly HttpClient _httpClient;

    private SomeService(HttpClient httpclient) => _httpClient = httpClient;

    public static ISomeService UsingHttpClient(HttpClient httpClient) => new SomeService(httpClient);

    //...some other code invoking SomeEvent
}

Я пытался создать метод для возврата EventHandler, но это, очевидно, неправильно и приводит к ошибке: public EventHandler<bool> GetBusyEvent => BusyEvent;.

Я также пытался перечислить делегат EventHandler в интерфейсе ISomeService, но это дает ошибку в VS2019: «Интерфейсы не могут содержать поля экземпляра».

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

Ответы [ 2 ]

1 голос
/ 21 марта 2020

Разницу между событием и делегатом в C# довольно сложно объяснить.

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

public EventHandler<bool> SomeEvent;

объявляет публикуемое c поле SomeEvent.

. Код клиента может добавлять функции в этот список и удалять их, что является разумным решением. Но клиентский код может также вызывать функции в списке, которые нам не нужны. Это должно быть делом класса SomeService.

Поэтому мы хотим, чтобы операции add и remove (+ = и - =) были опубликованы c, а вызовы были приватными.

Это именно то, что делает ключевое слово события.

public event EventHandler<bool> SomeEvent;

Приведенный выше код фактически создает три элемента в классе:

  1. Закрытое поле типа EventHandler
  2. Метод publi c с именем add_SomeEvent
  3. Метод publi c с именем remove_SomeEvent

Вызов SomeEvent + = или SomeEvent- = отображается на эти последние два.

Вот как это выглядит в ILDASM:

ILDASM for an event

Теперь интерфейс может указывать только методы (свойство, являющееся пара методов), поэтому, когда мы объявляем событие в интерфейсе, это означает, что класс, реализующий интерфейс, должен предоставить методы добавления и удаления (2 и 3 выше). На самом деле он не требует, чтобы класс предоставлял приватное поле.

Однако наиболее распространенный способ реализации события из интерфейса - это объявление его таким же образом, что дает нам два метода и поле, которое является бонусом (что касается интерфейса).

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

1 голос
/ 21 марта 2020

Если у вас есть ссылка на ISomeService, которая, как вы знаете, SomeService, вы можете разыграть ее.

Например,

ISomeService svc = SomeService.UsingHttpClient(client);
if (svc is SomeService ss)
{
    ss.SomeEvent += (s,a) =>
    {
        //whatever
    };
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...