Я розумію мету подій, особливо в контексті створення інтерфейсів користувача. Я думаю, що це прототип створення події:
public void EventName(object sender, EventArgs e);
Що роблять обробники подій, навіщо вони потрібні та як я їх створюю?
Я розумію мету подій, особливо в контексті створення інтерфейсів користувача. Я думаю, що це прототип створення події:
public void EventName(object sender, EventArgs e);
Що роблять обробники подій, навіщо вони потрібні та як я їх створюю?
Відповіді:
Щоб зрозуміти обробників подій, вам потрібно зрозуміти делегатів . У C # ви можете розглядати делегата як вказівник (або посилання) на метод. Це корисно, оскільки вказівник може бути переданий навколо як значення.
Центральною концепцією делегата є його підпис, або форма. Це (1) тип повернення та (2) вхідні аргументи. Наприклад, якщо ми створимо делегата void MyDelegate(object sender, EventArgs e)
, він може вказувати лише на методи, які повертаються void
, і приймають object
і EventArgs
. Вигляд як квадратний отвір і квадратний кілочок. Тому ми кажемо, що ці методи мають ту саму підпис або форму, що і делегат.
Отже, знаючи, як створити посилання на метод, давайте подумаємо про мету подій: ми хочемо привести якийсь код до виконання, коли щось відбувається в іншому місці системи - або "обробляти подію". Для цього ми створюємо конкретні методи для коду, який ми хочемо виконати. Клей між подією та способами, які потрібно виконати, - це делегати. Подія повинна внутрішньо зберігати "список" покажчиків до методів виклику, коли подія піднімається. * Звичайно, щоб мати можливість викликати метод, ми повинні знати, які аргументи передавати йому! Ми використовуємо делегата як "договір" між подією та всіма конкретними методами, які будуть викликані.
Тож за замовчуванням EventHandler
(і багатьом він подобається) представляє конкретну форму методу (знову ж таки, void / object-EventArgs). Коли ви оголошуєте подію, ви говорите, до якої форми методу (EventHandler) буде викликати цю подію, вказавши делегата:
//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);
//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;
//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
//Do some stuff
}
//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);
//To raise the event within a method.
SomethingHappened("bar");
(* Це ключ до подій у .NET і знімає "магію" - подія насправді під кришками є лише списком методів тієї самої "форми". Список зберігається там, де живе подія. Коли подія "підвищена", це дійсно просто "пройти цей список методів і викликати кожен з них, використовуючи ці значення в якості параметрів". Призначення обробника подій - це просто красивіший і простіший спосіб додавання вашого методу до цього списку методів Бути покликаним).
C # знає два терміни, delegate
і event
. Почнемо з першого.
A delegate
- це посилання на метод. Так само, як ви можете створити посилання на екземпляр:
MyClass instance = myFactory.GetInstance();
Ви можете використовувати делегата для створення посилання на метод:
Action myMethod = myFactory.GetInstance;
Тепер, коли у вас є посилання на метод, ви можете викликати метод через посилання:
MyClass instance = myMethod();
Але чому б ти це зробив? Ви також можете просто зателефонувати myFactory.GetInstance()
безпосередньо. У цьому випадку можна. Однак є багато випадків, коли потрібно задуматися про те, де ви не хочете, щоб решта програми мала знання myFactory
або myFactory.GetInstance()
безпосередньо дзвонила.
Очевидним є те, якщо ви хочете мати можливість замінити myFactory.GetInstance()
його myOfflineFakeFactory.GetInstance()
з одного центрального місця (він же фабричний шаблон методу ).
Отже, якщо у вас є TheOtherClass
клас, і йому потрібно використовувати. Ось так myFactory.GetInstance()
виглядатиме код без делегатів (вам потрібно повідомити TheOtherClass
про тип свого myFactory
):
TheOtherClass toc;
//...
toc.SetFactory(myFactory);
class TheOtherClass
{
public void SetFactory(MyFactory factory)
{
// set here
}
}
Якщо ви використовуєте делегатів, вам не доведеться виставляти тип мого заводу:
TheOtherClass toc;
//...
Action factoryMethod = myFactory.GetInstance;
toc.SetFactoryMethod(factoryMethod);
class TheOtherClass
{
public void SetFactoryMethod(Action factoryMethod)
{
// set here
}
}
Таким чином, ви можете надати делегата іншому класу для використання, не піддаючи їх своєму типу. Єдине, що ви відкриваєте, - це підпис вашого методу (скільки у вас параметрів і таких).
"Підпис мого методу", де я це чув раніше? О так, інтерфейси !!! інтерфейси описують підпис цілого класу. Подумайте, як делегати описують підпис лише одного методу!
Ще одна велика різниця між інтерфейсом та делегатом полягає в тому, що, коли ви пишете свій клас, вам не потрібно говорити на C # "цей метод реалізує цей тип делегата". За допомогою інтерфейсів вам потрібно сказати "цей клас реалізує цей тип інтерфейсу".
Далі, посилання на делегата може (з деякими обмеженнями, див. Нижче) посилатися на декілька методів (званих MulticastDelegate
). Це означає, що при виклику делегата буде виконано кілька явно прикріплених методів. Посилання на об'єкт завжди може посилатися лише на один об'єкт.
Обмеженнями для a MulticastDelegate
є те, що підпис (метод / делегат) не повинен мати значення повернення ( void
) та ключові слова out
та ref
не використовується в підписі. Очевидно, що ви не можете викликати два методи, які повертають число, і очікують, що вони повернуть одне і те ж число. Після того, як підпис відповідає, делегат автоматично a MulticastDelegate
.
Події - це лише властивості (як, наприклад, get; set; властивості до примірників полів), які піддають передплату на делегата від інших об'єктів. Однак ці властивості не підтримують get; set ;. Натомість вони підтримують додавання; видалити;
Отже, ви можете мати:
Action myField;
public event Action MyProperty
{
add { myField += value; }
remove { myField -= value; }
}
Отже, тепер ми знаємо, що делегат - це посилання на метод, і ми можемо мати подію, щоб повідомити світові, що вони можуть дати нам свої методи, на які можна посилатись від нашого делегата, і ми є кнопкою інтерфейсу користувача, значить: можна попросити кожного, хто цікавиться, чи мене натискали, зареєструвати у нас їх метод (через подію, яку ми експонували). Ми можемо використовувати всі ті методи, які були нам надані, і посилатися на них нашим делегатом. І тоді ми будемо чекати і чекати .... поки прийде користувач і натисне на цю кнопку, тоді у нас буде достатньо підстав викликати делегата. А оскільки делегат посилається на всі ті методи, які нам дано, всі ці методи будуть використані. Ми не знаємо, що роблять ці методи, і не знаємо, який клас реалізує ці методи. Все, що нам важливо, це те, що хтось зацікавився, щоб нас натискали,
Такі мови, як Java, не мають делегатів. Вони замість цього використовують інтерфейси. Як вони це роблять, - це попросити всіх, хто зацікавлений у тому, щоб нас "натискали", реалізувати певний інтерфейс (певним методом, який ми можемо викликати), а потім дати нам весь екземпляр, який реалізує інтерфейс. Ми зберігаємо список усіх об'єктів, що реалізують цей інтерфейс, і можемо викликати їх "певний метод, який ми можемо викликати", коли ми натискаємо.
event
це всього лише синтаксис, більше нічого.
Це фактично декларація для обробника подій - метод, який буде викликаний при запуску події. Щоб створити подію, слід написати щось подібне:
public class Foo
{
public event EventHandler MyEvent;
}
І тоді ви можете підписатися на подію так:
Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);
З OnMyEvent () визначено так:
private void OnMyEvent(object sender, EventArgs e)
{
MessageBox.Show("MyEvent fired!");
}
Кожного разу, коли Foo
пожежа викличе MyEvent
, тоді OnMyEvent
викличуть вашого обробника.
Вам не завжди потрібно використовувати екземпляр EventArgs
як другий параметр. Якщо ви хочете включити додаткову інформацію, ви можете використовувати клас, похідний від EventArgs
( EventArgs
є базовою умовою). Наприклад, якщо ви подивитеся на деякі події, визначені Control
в WinForms або FrameworkElement
WPF, ви можете побачити приклади подій, які передають додаткову інформацію обробникам подій.
OnXXX
шаблон іменування для вашого обробника подій. (Тупо вважається, що OnXXX означає «обробляти XXX» у MFC, а «піднімати XXX» у .net, і тепер його значення незрозуміле та заплутане - детальніше див. У цій публікації ). Кращими іменами було б RaiseXXX
викликати події та / HandleXXX
або Sender_XXX
для обробників подій.
Ось приклад коду, який може допомогти:
using System;
using System.Collections.Generic;
using System.Text;
namespace Event_Example
{
// First we have to define a delegate that acts as a signature for the
// function that is ultimately called when the event is triggered.
// You will notice that the second parameter is of MyEventArgs type.
// This object will contain information about the triggered event.
public delegate void MyEventHandler(object source, MyEventArgs e);
// This is a class which describes the event to the class that receives it.
// An EventArgs class must always derive from System.EventArgs.
public class MyEventArgs : EventArgs
{
private string EventInfo;
public MyEventArgs(string Text) {
EventInfo = Text;
}
public string GetInfo() {
return EventInfo;
}
}
// This next class is the one which contains an event and triggers it
// once an action is performed. For example, lets trigger this event
// once a variable is incremented over a particular value. Notice the
// event uses the MyEventHandler delegate to create a signature
// for the called function.
public class MyClass
{
public event MyEventHandler OnMaximum;
private int i;
private int Maximum = 10;
public int MyValue
{
get { return i; }
set
{
if(value <= Maximum) {
i = value;
}
else
{
// To make sure we only trigger the event if a handler is present
// we check the event to make sure it's not null.
if(OnMaximum != null) {
OnMaximum(this, new MyEventArgs("You've entered " +
value.ToString() +
", but the maximum is " +
Maximum.ToString()));
}
}
}
}
}
class Program
{
// This is the actual method that will be assigned to the event handler
// within the above class. This is where we perform an action once the
// event has been triggered.
static void MaximumReached(object source, MyEventArgs e) {
Console.WriteLine(e.GetInfo());
}
static void Main(string[] args) {
// Now lets test the event contained in the above class.
MyClass MyObject = new MyClass();
MyObject.OnMaximum += new MyEventHandler(MaximumReached);
for(int x = 0; x <= 15; x++) {
MyObject.MyValue = x;
}
Console.ReadLine();
}
}
}
OnMaximum?.Invoke(this,new MyEventArgs("you've entered..."));
Просто додати до існуючих чудових відповідей тут - спираючись на код у прийнятому, який використовує delegate void MyEventHandler(string foo)
...
Оскільки компілятор знає тип делегата події SomethingHappened , це:
myObj.SomethingHappened += HandleSomethingHappened;
Цілком еквівалентний:
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);
І обробники також можуть бути незареєстровані з -=
таким:
// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;
Для повноти підняття події можна зробити так, тільки в класі, який є власником події:
//Firing the event is done by simply providing the arguments to the event:
var handler = SomethingHappened; // thread-local copy of the event
if (handler != null) // the event is null if there are no listeners!
{
handler("Hi there!");
}
Нитка-локальна копія обробника необхідно , щоб переконатися , що виклик є поточно-- в іншому випадку потік може йти і незареєстрований останній обробник події відразу ж після того, як ми перевірили , якщо це було null
, і ми б мати «задоволення» NullReferenceException
там .
C # 6 представив приємну коротку руку для цього шаблону. Він використовує оператор нульового поширення.
SomethingHappened?.Invoke("Hi there!");
Моє розуміння подій таке;
Делегат:
Змінна, яка містить посилання на метод / методи, які потрібно виконати. Це дає можливість обходити такі методи, як змінна.
Кроки для створення та виклику події:
Подія є примірником делегата
Оскільки подія є екземпляром делегата, то ми повинні спершу визначити делегата.
Призначте метод / методи, які потрібно виконати при запуску події ( Виклик делегата )
Пожар події ( зателефонуйте делегату )
Приклад:
using System;
namespace test{
class MyTestApp{
//The Event Handler declaration
public delegate void EventHandler();
//The Event declaration
public event EventHandler MyHandler;
//The method to call
public void Hello(){
Console.WriteLine("Hello World of events!");
}
public static void Main(){
MyTestApp TestApp = new MyTestApp();
//Assign the method to be called when the event is fired
TestApp.MyHandler = new EventHandler(TestApp.Hello);
//Firing the event
if (TestApp.MyHandler != null){
TestApp.MyHandler();
}
}
}
}
видавець: там, де відбуваються події. Видавець повинен вказати, який делегат використовує клас та генерувати необхідні аргументи, передати ці аргументи та себе делегату.
абонент: там, де відбувається відповідь. Абонент повинен вказати методи реагування на події. Ці методи повинні приймати аргументи того ж типу, що і делегат. Потім підписник додає цей метод до делегата видавця.
Тому, коли подія трапиться у видавця, делегат отримає деякі аргументи події (дані тощо), але видавець не має уявлення, що буде з усіма цими даними. Підписники можуть створювати у своєму класі методи реагування на події в класі видавця, щоб підписники могли реагувати на події видавця.
//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyDelegate(string foo);
//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyDelegate MyEvent;
//Here is some code I want to be executed
//when SomethingHappened fires.
void MyEventHandler(string foo)
{
//Do some stuff
}
//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);
Я погоджуюся з KE50, за винятком того, що ключове слово "подія" розглядаю як псевдонім для "ActionCollection", оскільки подія містить сукупність дій, які потрібно виконати (тобто делегат).
using System;
namespace test{
class MyTestApp{
//The Event Handler declaration
public delegate void EventAction();
//The Event Action Collection
//Equivalent to
// public List<EventAction> EventActions=new List<EventAction>();
//
public event EventAction EventActions;
//An Action
public void Hello(){
Console.WriteLine("Hello World of events!");
}
//Another Action
public void Goodbye(){
Console.WriteLine("Goodbye Cruel World of events!");
}
public static void Main(){
MyTestApp TestApp = new MyTestApp();
//Add actions to the collection
TestApp.EventActions += TestApp.Hello;
TestApp.EventActions += TestApp.Goodbye;
//Invoke all event actions
if (TestApp.EventActions!= null){
//this peculiar syntax hides the invoke
TestApp.EventActions();
//using the 'ActionCollection' idea:
// foreach(EventAction action in TestApp.EventActions)
// action.Invoke();
}
}
}
}
Чудові технічні відповіді у пості! Мені нічого технічно не додати.
Однією з головних причин появи нових функцій у мовах та програмному забезпеченні взагалі є маркетинг чи політика компанії! :-) Це не повинно бути заниженим!
Я думаю, що це стосується певних делегатів і подій! я вважаю їх корисними та додаю цінність мові C #, але з іншого боку, мова Java вирішила не використовувати їх! вони вирішили, що все, що ви вирішуєте з делегатами, ви вже можете вирішити за допомогою існуючих особливостей мови, тобто інтерфейсів, наприклад
Зараз близько 2001 року Microsoft випустила рамку .NET і мову C # як конкурентне рішення для Java, тому було добре мати НОВІ ОСОБЛИВОСТІ, яких у Java немає.
Нещодавно я зробив приклад того, як використовувати події в c #, і розмістив його у своєму блозі. Я намагався зробити це максимально зрозумілим, дуже простим прикладом. Якщо це може допомогти комусь, ось це: http://www.konsfik.com/using-events-in-csharp/
Він включає опис та вихідний код (з великою кількістю коментарів), і він головним чином фокусується на правильному (шаблоноподібному) використанні подій та обробників подій.
Деякі ключові моменти:
Події схожі на "підтипи делегатів", лише більш обмежені (в хороший спосіб). Насправді декларація події завжди включає делегата (EventHandlers є типом делегата).
Обробники подій - це конкретні типи делегатів (ви можете вважати їх шаблоном), які змушують користувача створювати події, які мають певний "підпис". Підпис має такий формат: (відправник об'єкта, навіть події аргументів EventArgs).
Ви можете створити власний підклас EventArgs, щоб включити будь-який тип інформації, яку потрібно передати події. Не потрібно використовувати EventHandlers під час використання подій. Ви можете повністю їх пропустити і використовувати власний вид делегата на їх місці.
Однією з ключових відмінностей між використанням подій та делегатами є те, що події можна викликати лише з класу, в якому вони були оголошені, навіть якщо вони можуть бути оголошені як загальнодоступні. Це дуже важлива відмінність, оскільки вона дозволяє виставляти ваші події так, щоб вони були "пов'язані" із зовнішніми методами, і в той же час були захищені від "зовнішнього нецільового використання".
Інша річ, про яку потрібно знати , в деяких випадках вам доведеться використовувати Делегатів / Події, коли вам потрібен низький рівень зв'язку !
Якщо ви хочете використовувати компонент у декількох місцях додатка , вам потрібно зробити компонент з низьким рівнем зв'язку, а специфічна незахищена LOGIC повинна бути делегована ВНУТРІСТЬ вашого компонента! Це гарантує наявність у вас роз'єднаної системи та більш чистий код.
У принципі SOLID це " D ", ( принцип інверсії D- залежності).
Також відомий як " IoC ", Інверсія управління .
Ви можете зробити " IoC " за допомогою подій, делегатів та DI (введення залежності).
Доступ до методу в дитячому класі легко. Але складніше отримати доступ до методу в батьківському класі від дитини. Ви повинні передати батьківське посилання на дитину! (або використовувати DI з інтерфейсом)
Делегати / події дозволяють нам спілкуватися від дитини до батька без посилання!
На цій діаграмі вище я не використовую Delegate / Event, і батьківський компонент B повинен мати посилання на батьківський компонент A для виконання незахищеної бізнес-логіки методом A. (високий рівень з'єднання)
При такому підході мені доведеться скласти всі посилання на всі компоненти, які використовують компонент B! :(
На цій діаграмі вище я використовую Delegate / Event і компонент B не повинен бути відомим A. (низький рівень зв'язку)
І ви можете використовувати свій компонент B в будь-якому місці вашої програми !