Обработка событий С# (по сравнению с Java)
В настоящее время у меня есть понимание и реализация событий в С# с использованием delagates. Я привык к Java-способу делать вещи:
- Определите интерфейс для типа слушателя, который будет содержать ряд определений методов.
- Определите класс адаптера для этого интерфейса, чтобы упростить работу, если меня не интересуют все события, определенные в прослушивателе
- Определить методы добавления, удаления и получения [] в классе, который вызывает события
- Определите защищенные методы пожара, чтобы выполнить грязную работу по переходу через список добавленных слушателей и вызывать правильный метод.
Это я понимаю (и люблю!). Я знаю, что я мог бы сделать это точно так же в С#, но кажется, что для С# существует новая (лучше?) система. После прочтения бесчисленных уроков, объясняющих использование делегатов и событий в С#, я по-прежнему не понимаю, что происходит дальше: S
Короче говоря, для следующих методов, как реализовать систему событий в С#:
void computerStarted(Computer computer);
void computerStopped(Computer computer);
void computerReset(Computer computer);
void computerError(Computer computer, Exception error);
^ Вышеупомянутые методы берутся из приложения Java, которое я когда-то сделал, который я пытаюсь передать на С#.
Большое спасибо!
Ответы
Ответ 1
Вы должны создать четыре события и методы их повышения вместе с новым классом на основе 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 для каждого события
- Объявления о событиях представляют собой события, подобные полям. То, что на самом деле создается, является переменной и событием. Внутри класса вы видите переменную; вне класса вы видите событие.
См. статью события/делегаты для более подробной информации о событиях.
Ответ 2
Вам нужно будет определить один делегат для этого
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.
}
Ответ 3
Основное отличие состоит в том, что в С# события не основаны на интерфейсах. Вместо этого издатель событий объявляет делегата, который вы можете рассматривать как указатель на функцию (хотя не совсем то же самое:-)). Затем абонент реализует прототип события как обычный метод и добавляет новый экземпляр делегата в цепочку обработчиков событий издателя. Подробнее о делегаты и события.
Вы также можете прочитать краткое сравнение событий С# и Java здесь.
Ответ 4
Прежде всего, в .Net используется стандартная сигнатура метода, которая обычно используется для событий. Языки позволяют использовать какую-либо сигнатуру метода для всех событий, и есть некоторые эксперты, которые считают, что соглашение является ошибочным (я в основном согласен), но это то, что есть, и я буду следовать ему для этого примера.
- Создайте класс, который будет содержать параметры событий (полученные из EventArgs).
public class ComputerEventArgs : EventArgs
{
Computer computer;
// constructor, properties, etc.
}
- Создайте публичное событие в классе, который должен запустить событие.
class ComputerEventGenerator // I picked a terrible name BTW.
{
public event EventHandler<ComputerEventArgs> ComputerStarted;
public event EventHandler<ComputerEventArgs> ComputerStopped;
public event EventHandler<ComputerEventArgs> ComputerReset;
...
}
- Вызовите события.
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
}
}
- Прикрепите обработчик для события.
void OnLoad()
{
ComputerEventGenerator computerEventGenerator = new ComputerEventGenerator();
computerEventGenerator.ComputerStarted += new EventHandler<ComputerEventArgs>(ComputerEventGenerator_ComputerStarted);
}
- Создайте обработчик, который вы только что подключили (в основном, нажав клавишу Tab в VS).
private void ComputerEventGenerator_ComputerStarted(object sender, ComputerEventArgs args)
{
if (args.Computer.Name == "HAL9000")
ShutItDownNow(args.Computer);
}
- Не забудьте отсоединить обработчик, когда закончите. (Забыть сделать это - самый большой источник утечек памяти в С#!)
void OnClose()
{
ComputerEventGenerator.ComputerStarted -= ComputerEventGenerator_ComputerStarted;
}
И что это!
EDIT: Я честно не могу понять, почему мои пронумерованные точки выглядят как "1". Я ненавижу компьютеры.
Ответ 5
существует несколько способов сделать то, что вы хотите. Самый прямой способ - определить делегаты для каждого события в классе хостинга, например.
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 для состояний компьютера в случае последующего расширения. Но основная методика остается прежней:
- делегат определяет подпись/интерфейс для обработчика/прослушивателя событий
- членом данных событий является список "слушателей"
прослушиватели удаляются с помощью синтаксиса = = вместо + =
Ответ 6
В событиях С# есть делегаты. Они ведут себя аналогично указателю на функции в 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.
Ответ 7
Делегат объявляет подпись функции, и когда он используется как событие в классе, он также действует как совокупность зачисленных целей вызова. Синтаксис + = и - = для события используется для добавления цели в список.
Учитывая следующие делегаты, используемые в качестве событий:
// 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});
}
}
Вы должны проверить значение null, если в событие не добавлены делегаты. Когда вы вызываете вызов метода, все вызовы, которые были добавлены, будут вызваны. Вот почему для событий тип возврата недействителен - нет единственного значения возврата - поэтому для обратной передачи информации у вас будут свойства в EventArg, которые будут обрабатываться обработчиками событий.
Еще одно уточнение состоит в том, чтобы использовать общий делегат EventHandler, а не объявлять конкретный делегат для каждого типа аргументов.
public class Thing
{
public event EventHandler<ComputerEventArgs> Started;
public event EventHandler<ComputerEventArgs> Stopped;
public event EventHandler<ComputerEventArgs> Reset;
public event EventHandler<ComputerErrorEventArgs> Error;
}
Ответ 8
Спасибо всем за ответы! Наконец, я начинаю понимать, что происходит. Только одно; Кажется, что если у каждого события было другое число/тип аргументов, мне нужно было бы создать другой класс:: 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 по умолчанию)
Ура!
Ответ 9
ОК, ОКОНЧАТЕЛЬНОЕ разъяснение!: Так что это в значительной степени лучшее, что я могу сделать кодовым способом для реализации этих событий?
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));
}
}
}