C # обработка событий (по сравнению с Java) - PullRequest
11 голосов
/ 08 октября 2008

В настоящее время мне трудно понять и реализовать события в C # с использованием delagates. Я привык к способу выполнения вещей на Java:

  1. Определить интерфейс для типа слушателя, который будет содержать несколько определений методов
  2. Определите класс адаптера для этого интерфейса, чтобы упростить ситуацию, если меня не интересуют все события, определенные в слушателе
  3. Определить методы Add, Remove и Get [] в классе, который вызывает события
  4. Определите методы защищенного огня, чтобы выполнить грязную работу, перебирая список добавленных слушателей и вызывая правильный метод

Это я понимаю (и мне нравится!) - я знаю, что мог бы сделать то же самое в c #, но, похоже, для c # существует новая (лучшая?) Система. После прочтения бесчисленных руководств, объясняющих использование делегатов и событий в c #, я все еще не ближе к реальному пониманию того, что происходит: S


Вкратце, для следующих методов, как бы я внедрил систему событий в c #:

void computerStarted(Computer computer);
void computerStopped(Computer computer);
void computerReset(Computer computer);
void computerError(Computer computer, Exception error);

^ Вышеуказанные методы взяты из Java-приложения, которое я однажды сделал, и которое я пытаюсь перенести на c #.

Большое спасибо!

Ответы [ 9 ]

16 голосов
/ 08 октября 2008

Вы бы создали четыре события и методы для их вызова вместе с новым классом на основе EventArgs для указания ошибки:

public class ExceptionEventArgs : EventArgs
{
    private readonly Exception error;

    public ExceptionEventArgs(Exception error)
    {
         this.error = error;
    }

    public Error
    {
         get { return error; }
    }
}

public class Computer
{
    public event EventHandler Started = delegate{};
    public event EventHandler Stopped = delegate{};
    public event EventHandler Reset = delegate{};
    public event EventHandler<ExceptionEventArgs> Error = delegate{};

    protected void OnStarted()
    {
        Started(this, EventArgs.Empty);
    }

    protected void OnStopped()
    {
        Stopped(this, EventArgs.Empty);
    }

    protected void OnReset()
    {
        Reset(this, EventArgs.Empty);
    }

    protected void OnError(Exception e)
    {
        Error(this, new ExceptionEventArgs(e));
    }
}

Затем классы будут подписываться на событие, используя метод или анонимную функцию:

someComputer.Started += StartEventHandler; // A method
someComputer.Stopped += delegate(object o, EventArgs e)
{ 
    Console.WriteLine("{0} has started", o);
};
someComputer.Reset += (o, e) => Console.WriteLine("{0} has been reset");

Несколько замечаний по поводу вышесказанного:

  • Методы OnXXX защищены, чтобы производные классы могли вызывать события. Это не всегда необходимо - делайте так, как считаете нужным.
  • Элемент delegate{} в каждом объявлении события - всего лишь уловка, чтобы избежать необходимости делать нулевую проверку. Он подписывает обработчик события no-op на каждое событие
  • Объявления о событиях подобные событиям поля . Фактически создается переменная и событие. Внутри класса вы видите переменную; вне класса вы видите событие.

См. Мою статью о событиях / делегатах для более подробной информации о событиях.

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

Для этого вам нужно будет определить одного делегата

public delegate void ComputerEvent(object sender, ComputerEventArgs e);

ComputerEventArgs будет определен так:

public class ComputerEventArgs : EventArgs
{
    // TODO wrap in properties
    public Computer computer;
    public Exception error;

    public ComputerEventArgs(Computer aComputer, Exception anError)
    {
        computer = aComputer;
        error = anError;
    }

    public ComputerEventArgs(Computer aComputer) : this(aComputer, null)
    {
    }
}

Класс, который запускает события, будет иметь:

public YourClass
{
    ...
    public event ComputerEvent ComputerStarted;
    public event ComputerEvent ComputerStopped;
    public event ComputerEvent ComputerReset;
    public event ComputerEvent ComputerError;
    ...
}

Вот как вы назначаете обработчики для событий:

YourClass obj = new YourClass();
obj.ComputerStarted += new ComputerEvent(your_computer_started_handler);

Ваш обработчик:

private void ComputerStartedEventHandler(object sender, ComputerEventArgs e)
{
   // do your thing.
}
4 голосов
/ 08 октября 2008

Основное отличие состоит в том, что в C # события не основаны на интерфейсе. Вместо этого издатель событий объявляет делегата, который вы можете рассматривать как указатель на функцию (хотя это не совсем то же самое :-)). Затем подписчик реализует прототип события как обычный метод и добавляет новый экземпляр делегата в цепочку обработчика событий издателя. Подробнее о делегатах и событиях .

Вы также можете прочитать краткое сравнение событий C # и Java здесь .

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

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

  1. Создайте класс, который будет содержать параметры события (производные от EventArgs).
public class ComputerEventArgs : EventArgs 
{
  Computer computer; 
  // constructor, properties, etc.
}
  1. Создайте публичное событие в классе, которое должно инициировать событие.
    class ComputerEventGenerator  // I picked a terrible name BTW.
    {
      public event EventHandler<ComputerEventArgs> ComputerStarted;
      public event EventHandler<ComputerEventArgs> ComputerStopped;
      public event EventHandler<ComputerEventArgs> ComputerReset;
    ...
    }
  1. Назовите события.
    class ComputerEventGenerator
    {
    ...
      private void OnComputerStarted(Computer computer) 
      {
        EventHandler<ComputerEventArgs> temp = ComputerStarted;
        if (temp != null) temp(this, new ComputerEventArgs(computer)); // replace "this" with null if the event is static
      }
     }
  1. Прикрепить обработчик для события.
    void OnLoad()
    {
      ComputerEventGenerator computerEventGenerator = new ComputerEventGenerator();
      computerEventGenerator.ComputerStarted += new  EventHandler<ComputerEventArgs>(ComputerEventGenerator_ComputerStarted);
    }
  1. Создайте обработчик, который вы только что подключили (в основном нажатием клавиши Tab в VS).
    private void ComputerEventGenerator_ComputerStarted(object sender, ComputerEventArgs args)
    {
      if (args.Computer.Name == "HAL9000")
         ShutItDownNow(args.Computer);
    }
  1. Не забудьте отсоединить обработчик, когда закончите. (Забывание этого - самый большой источник утечек памяти в C #!)
    void OnClose()
    {
      ComputerEventGenerator.ComputerStarted -= ComputerEventGenerator_ComputerStarted;
    }

И это все!

РЕДАКТИРОВАТЬ: я, честно говоря, не могу понять, почему все мои пронумерованные точки отображаются как «1». Я ненавижу компьютеры.

1 голос
/ 08 октября 2008

Делегат объявляет сигнатуру функции, и когда она используется как событие в классе, она также действует как набор зачисленных целей вызова. Синтаксис + = и - = для события используется для добавления цели в список.

Учитывая следующие делегаты, используемые в качестве событий:

// arguments for events
public class ComputerEventArgs : EventArgs
{
    public Computer Computer { get; set; }
}

public class ComputerErrorEventArgs : ComputerEventArgs
{
    public Exception Error  { get; set; }
}

// delegates for events
public delegate void ComputerEventHandler(object sender, ComputerEventArgs e);

public delegate void ComputerErrorEventHandler(object sender, ComputerErrorEventArgs e);

// component that raises events
public class Thing
{
    public event ComputerEventHandler Started;
    public event ComputerEventHandler Stopped;
    public event ComputerEventHandler Reset;
    public event ComputerErrorEventHandler Error;
}

Вы бы подписались на эти события со следующим:

class Program
{
    static void Main(string[] args)
    {
        var thing = new Thing();
        thing.Started += thing_Started;
    }

    static void thing_Started(object sender, ComputerEventArgs e)
    {
        throw new NotImplementedException();
    }
}

Хотя аргументы могут быть любыми, объект-отправитель и EventArgs e - это соглашение, которое используется очень последовательно. + = Thing_started сначала создаст экземпляр делегата, указывающего на целевой метод, а затем добавит его к событию.

В самом компоненте вы обычно добавляете методы для запуска событий:

public class Thing
{
    public event ComputerEventHandler Started;

    public void OnStarted(Computer computer)
    {
        if (Started != null)
            Started(this, new ComputerEventArgs {Computer = computer});
    }
}

Вы должны проверить на нулевое значение, если к событию не было добавлено ни одного делегата. Однако при выполнении вызова метода будут вызваны все добавленные делегаты. Вот почему для событий тип возвращаемого значения void - единого возвращаемого значения нет - поэтому для передачи информации у вас будут свойства в EventArgs, которые обработчики событий будут изменять.

Еще одним уточнением было бы использование общего делегата EventHandler, а не объявление конкретного делегата для каждого типа аргументов.

public class Thing
{
    public event EventHandler<ComputerEventArgs> Started;
    public event EventHandler<ComputerEventArgs> Stopped;
    public event EventHandler<ComputerEventArgs> Reset;
    public event EventHandler<ComputerErrorEventArgs> Error;
}
1 голос
/ 08 октября 2008

В c # события проходят делегаты. Они ведут себя аналогично указателю на функцию в C / C ++, но являются фактическими классами, производными от System.Delegate.

В этом случае создайте собственный класс EventArgs для передачи объекта Computer.

public class ComputerEventArgs : EventArgs
{
  private Computer _computer;

  public ComputerEventArgs(Computer computer) {
    _computer = computer;
  }

  public Computer Computer { get { return _computer; } }
}

Затем выставьте события от производителя:

public class ComputerEventProducer
{
  public event EventHandler<ComputerEventArgs> Started;
  public event EventHandler<ComputerEventArgs> Stopped;
  public event EventHandler<ComputerEventArgs> Reset;
  public event EventHandler<ComputerEventArgs> Error;

  /*
  // Invokes the Started event */
  private void OnStarted(Computer computer) {
    if( Started != null ) {
      Started(this, new ComputerEventArgs(computer));
    }
  }

  // Add OnStopped, OnReset and OnError

}

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

public class ComputerEventConsumer
{
  public void ComputerEventConsumer(ComputerEventProducer producer) {
    producer.Started += new EventHandler<ComputerEventArgs>(ComputerStarted);
    // Add other event handlers
  }

  private void ComputerStarted(object sender, ComputerEventArgs e) {
  }
}

Когда ComputerEventProducer вызывает OnStarted, вызывается событие Started, которое, в свою очередь, вызывает метод ComputerEventConsumer.ComputerStarted.

1 голос
/ 08 октября 2008

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

public delegate void ComputerStartedDelegate(Computer computer);
protected event ComputerStartedDelegate ComputerStarted;
public void OnComputerStarted(Computer computer)
{
    if (ComputerStarted != null)
    {
        ComputerStarted.Invoke(computer);
    }
}
protected void someMethod()
{
    //...
    computer.Started = true;  //or whatever
    OnComputerStarted(computer);
    //...
}

любой объект может «прослушать» это событие просто:

Computer comp = new Computer();
comp.ComputerStarted += new ComputerStartedDelegate(
    this.ComputerStartedHandler);

protected void ComputerStartedHandler(Computer computer)
{
    //do something
}

«Рекомендованным стандартным способом» для этого было бы определение подкласса EventArgs для хранения значений компьютера (и старого / нового состояния и исключения), сокращая 4 делегата до одного. В этом случае это было бы более чистое решение, особенно с Enum для состояний компьютера в случае последующего расширения. Но основная техника остается прежней:

  • делегат определяет сигнатуру / интерфейс для обработчика / прослушивателя событий
  • элемент данных события является списком «слушателей»

слушатели удаляются с использованием синтаксиса - = вместо + =

0 голосов
/ 08 октября 2008

Ладно, окончательное разъяснение !: Так что это лучшее, что я могу сделать с точки зрения кода для реализации этих событий?

   public class Computer {

        public event EventHandler Started;

        public event EventHandler Stopped;

        public event EventHandler Reset;

        public event EventHandler<BreakPointEvent> BreakPointHit;

        public event EventHandler<ExceptionEvent> Error;

        public Computer() {
            Started = delegate { };
            Stopped = delegate { };
            Reset = delegate { };
            BreakPointHit = delegate { };
            Error = delegate { };
        }

        protected void OnStarted() {
            Started(this, EventArgs.Empty);
        }

        protected void OnStopped() {
            Stopped(this, EventArgs.Empty);
        }

        protected void OnReset() {
            Reset(this, EventArgs.Empty);
        }

        protected void OnBreakPointHit(int breakPoint) {
            BreakPointHit(this, new BreakPointEvent(breakPoint));
        }

        protected void OnError(System.Exception exception) {
            Error(this, new ExceptionEvent(exception));
        }
    }
}
0 голосов
/ 08 октября 2008

Большое спасибо всем за ваши ответы! Наконец я начинаю понимать, что происходит. Только одна вещь; Кажется, что если бы у каждого события было разное количество / тип аргументов, мне нужно было бы создать отдельный класс :: EventArgs, чтобы справиться с ним:

public void computerStarted(Computer computer);
public void computerStopped(Computer computer);
public void computerReset(Computer computer);
public void breakPointHit(Computer computer, int breakpoint);
public void computerError(Computer computer, Exception exception);

Для этого потребуется три класса, чтобы разобраться с событиями !? (Ну, два пользовательских, и один с использованием класса EventArgs.Empty по умолчанию)

ура!

...