В настоящее время мне сложно понять и реализовать события на C# с помощью delagates. Я привык к тому, что делает Java:
Это я понимаю (и мне нравится!) - Я знаю, что могу сделать то же самое в 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#.
Огромное спасибо!





Вам нужно будет определить одного делегата для этого
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.
}
Я предпочитаю проводить отдельные мероприятия для каждого события. Я видел код, который использовал Enums для состояния компьютера, но происходит то, что обработчик будет иметь переключатель (e.State), а затем в любом случае вызывать разные методы для каждого состояния.
Вам не нужно определять своего собственного делегата - для этого нужен EventHandler <TEventArgs>. Также нетрадиционные названия событий - методы событий повышение обычно начинаются с «On», но сами события этого не делают. Например, событие Control.Click вызывается Control.OnClick ().
@Jon - круто. Я все еще использую C# 2.0, поэтому еще не пробовал.
EventHandler <TEventArgs> существует в C# 2.0
Основное отличие состоит в том, что в C# события не привязаны к интерфейсу. Вместо этого издатель события объявляет делегат, который вы можете рассматривать как указатель на функцию (хотя и не совсем то же самое :-)). Затем подписчик реализует прототип события как обычный метод и добавляет новый экземпляр делегата в цепочку обработчиков событий издателя. Узнайте больше о делегаты и События.
Вы также можете прочитать краткое сравнение событий C# и Java здесь.
Отличный ответ, вместо того, чтобы просто дать код, вы дали ему объяснение.
Дайте человеку рыбу, и он сможет съесть целый день. Научите человека ловить рыбу, и он сможет есть всю жизнь. :-)
есть несколько способов делать то, что вы хотите. Способ самый прямой заключался бы в определении делегатов для каждого события в классе размещения, например.
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 для состояний компьютера в случае более позднего расширения. Но основная техника осталась прежней:
слушатели удаляются с использованием синтаксиса - = вместо + =
В 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.
События - это делегаты нет. События - это то, на что вы подписываетесь.
Вы должны создать четыре события и методы для их возникновения, а также новый класс на основе 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");
Несколько замечаний по поводу вышеизложенного:
delegate{} в каждом объявлении события - это всего лишь уловка, позволяющая избежать проверки на null. Он подписывает обработчик событий без операции на каждое событие.См. Мою статью события / делегаты для более подробной информации о событиях.
Все выглядит хорошо, но мне не нравится шаблон добавления к событиям пустых делегатов. Я знаю, это означает, что вам не нужно проверять значение null, но это означает, что вы выделяете потраченную впустую память для каждого класса и всегда вызываете обратный вызов, даже если нет слушателей. Проверить на нуль несложно ... хотя +1
Это компромисс между очень небольшим падением производительности (действительно маленьким) и улучшением читабельности. Я всегда стремлюсь сначала к удобочитаемости, а затем к микрооптимизации, если это абсолютно необходимо.
Я не намерен преждевременно оптимизировать, но мне кажется, что есть разница между этим и неуместной неэффективностью из-за незначительного улучшения читабельности / удобства использования (и на самом деле я бы сказал, что это менее читабельно, потому что я ожидал чтобы увидеть нулевую проверку в правильном коде вызова события!).
Я бы сказал, что вы ожидаете увидеть нулевую проверку только потому, что вы не привыкли к шаблону - как только вы узнаете, что он есть и почему, я считаю, что это сделает его более читаемым. Это, конечно, мой опыт. На мой взгляд, удобство чтения стоит крошечного дополнительного объема памяти (продолжение)
если вы не знаете, что будете создавать лоты экземпляров. В этом случае я потенциально мог бы создать «постоянный» бесполезный делегат - без потерь памяти, кроме одного экземпляра, и только тривиальные затраты на выполнение без операций. Обратите внимание, что в многопоточном классе проверка на нулевое значение должна быть более сложной (продолжение)
во избежание состояния гонки - вам нужно назначить локальную переменную и сравнить который с null перед ее вызовом, а не использовать переменную-член дважды. Подобных тонкостей можно избежать благодаря тому, что фактически является реализацией шаблона нулевого объекта.
Мне тоже не нравится техника пустого делегата. Когда я смотрю на этот код, мне кажется, что он неправильный, но на самом деле это дело вкуса. Чтобы избежать проверки на null, я использую специальный метод расширения под названием Raise, который делает это за меня, и часто с общей версией EventArgs, которая имеет единственное свойство Item.
= delegate{}; действительно нужен?
@LeiYang: Если вы не хотите беспокоиться о нулевых значениях, да. В наши дни я бы не стал беспокоиться, а просто вызвал бы события через Stopped?.Invoke(this, EventArgs.Empty) (и т. д.), Но оператор, допускающий нулевое значение, не был доступен в 2008 году ...
Прежде всего, в .Net есть стандартная сигнатура метода, которая обычно используется для событий. Языки позволяют использовать любую сигнатуру метода для событий, и есть некоторые эксперты, которые считают, что соглашение ошибочно (я в основном согласен), но это то, что есть, и я буду следовать ему в этом примере.
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);
}
private void ComputerEventGenerator_ComputerStarted(object sender, ComputerEventArgs args)
{
if (args.Computer.Name == "HAL9000")
ShutItDownNow(args.Computer);
}
void OnClose()
{
ComputerEventGenerator.ComputerStarted -= ComputerEventGenerator_ComputerStarted;
}
Вот и все!
Обновлено: я, честно говоря, не могу понять, почему все мои пронумерованные баллы отображаются как «1». Ненавижу компьютеры.
Не знаю, почему редактор доставляет мне столько хлопот. Сначала блоки кода ничего не делали, кроме отступа, поэтому мне пришлось изменить его на предварительные теги. Тогда моя тщательно пронумерованная последовательность теперь состоит из единиц. Эта штука заставляет меня выглядеть нелепо!
Джеффри: Это потому, что нумерованные элементы списка должны располагаться в последовательном порядке. Вы чередуете код и списки, которые представляют собой два разных типа блоков.
Но я не закодировал список как список, я просто ввел сами числа. Он видит, где я набрал «3». цифры "о, это пронумерованный элемент. Я просто поменяю его на 1." Компьютеры!
Делегат объявляет сигнатуру функции, и когда он используется в качестве события в классе, он также действует как набор перечисленных целей вызова. Синтаксис + = и - = для события используется для добавления цели в список.
Учитывая, что в качестве событий используются следующие делегаты:
// 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, если к событию не были добавлены делегаты. Однако при вызове метода будут вызваны все добавленные делегаты. Вот почему для событий тип возвращаемого значения недействителен - нет единого возвращаемого значения - поэтому для обратной связи у вас должны быть свойства 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;
}
Спасибо вам всем за ответы! Наконец я начинаю понимать, что происходит. Только одно; Похоже, что если бы каждое событие имело разное количество / тип аргументов, мне нужно было бы создать другой класс :: 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 по умолчанию)
Ваше здоровье!
Как я уже сказал в своем ответе, некоторые эксперты считают, что конвенция .Net для событий некорректна. Вы только что выделили одно из главных возражений. (Другим является соглашение об отправителе объекта, которое не является необходимым и во многих случаях нарушает инкапсуляцию.) На практике функция EventArgs обычно работает нормально.
Кстати, я ожидаю, что вы прочитаете каждое слово каждого ответа, прежде чем выбрать победителя! ;-П
Ха-ха, я поддержал всех - все хорошие ответы. Что касается того, кто из них является «победителем» ... ах!
Хорошо, ЗАКЛЮЧИТЕЛЬНОЕ разъяснение! Значит, это лучшее, что я могу сделать с точки зрения кода для реализации этих событий?
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));
}
}
}
если вы используете Enum для состояний до / после, вам нужно только 1 событие, ComputerStateChanged