Руководство C# Delegate и Event

1- Что такое Delegate?

В C# каждая функция (формула, или constructor) имеет вид функции. Посмотрим на метод  SayHello ниже:
Полный пример:
HelloProgram.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class HelloProgram
    { 
        // Это метод с параметром string и возвращает string
        // Вид функции:  (string) -> (string)
        public string SayHello(string name)
        {
            return "Hello " + name;
        }
        
        // Это метод с 2-мя параметрами string, и возвращает string
        // Вид функции: (string, string) -> (string)
        public string SayHello(string firstName, string lastName)
        {
            return "Hello " + firstName + " " + lastName;
        }

        // Это метод с 1-м параметром, и ничего не возвращает.
        // Вид функции: (string) -> ()
        public void Silent(string name)
        {

        }
    }


}
Два метода ниже оба имеют одинаковый тип функции: 
C# использует ключевое слово  delegate (Делегат) чтобы дать определения для функций (метод или constructor) имеющие один вид функции.
Синтаксис:
// Синтаксис определяющий delegate:
delegate <return_type> <delegate_name> <parameter_list>
Пример:
// Определить вид представляющий функции с видом
// (string,string) -> (string).
private delegate string MyDelegate(string s1, string s2);

2- Пример с delegate

MathUtils.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class MathUtils
    {

        // (int, int)  -> (int)
        public static int sum(int a, int b)
        {
            return a + b;
        }

        // (int, int)  -> (int)
        public static int minus(int a, int b)
        {
            return a - b;
        }

        // (int, int)  -> (int)
        public static int multiple(int a, int b)
        {
            return a * b;
        }
 

    }

}
Пример внизу определяет delegate IntIntToInt представляющий функиции как  (int, int) -> (int).
MyFirstDelegate.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class MyFirstDelegate
    {
        // Определить 1 delegate
        // представляющий функции вида (int, int) -> (int)
        delegate int IntIntToInt(int a, int b);



        public static void Main(string[] args)
        {
            // Создать объект delegate.
            // Передать (pass) в параметр функцию одного вида с delegate.
            IntIntToInt iiToInt = new IntIntToInt(MathUtils.sum);

            // Когда вы выполняете один delegate.
            // Он вызовает функцию (или метод), который он представляет.
            int value = iiToInt(10, 20); // 30

            Console.WriteLine("Value = {0}", value);

            // Прикрепить другое значение к delegate.
            iiToInt = new IntIntToInt(MathUtils.multiple);

            value = iiToInt(10, 20); // 200

            Console.WriteLine("Value = {0}", value);

            Console.Read();

        }


    }


}
Запуск примера:
Вы так же можете создать  delegate представляющая нестатическую функцию (none static). Например:
HelloProgramTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class HelloProgramTest
    {
        // Создать Delegate
        // Представляющий видам функции: (string) -> (string).
        private delegate string StringStringToString(string s);



        public static void Main(string[] args)  
        {

            // Создать объект HelloProgram.
            HelloProgram program = new HelloProgram();

            // Создать объект Delegate представляющий функцию SayHello.
            StringStringToString ssToS = new StringStringToString(program.SayHello);


            // Test 
            string greeting = ssToS("Tom");

            Console.WriteLine(greeting);

            Console.Read();
        }

    }


}

3- Функция возвращает функцию

В C#, с помощью Delegate вы можете создать функцию возвращающую функцию (На самом деле функция возвращает Delegate).
 
TaxFormulas.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class TaxFormulas
    {
        // Delegate представляет функциям вида (float) -> (float).
        public delegate float TaxFormula(float salary);

        // Формула расчета налогов Америки (10% заработной платы).
        public static float UsaFormula(float salary)
        {
            return 10 * salary / 100;
        }

        // Формула расчета налогов Вьетнама (5% заработной платы).
        public static float VietnamFormula(float salary)
        {
            return 5 * salary / 100;
        }

        // Формула расчета налогов по умолчанию (7% заработной платы).
        public static float DefaultFormula(float salary)
        {
            return 7 * salary / 100;
        }

        // Возвращает функцию расчета налогов основываясь на коде страны. (VN, USA, ..)
        public static TaxFormula GetSalaryFormula(string countryCode)
        {
            if (countryCode == "VN")
            {
                return TaxFormulas.VietnamFormula;
            }
            else if (countryCode == "USA")
            {
                return TaxFormulas.UsaFormula;
            }
            return TaxFormulas.DefaultFormula;
        }

    }

}
TaxFormulaTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class TaxFormulaTest
    {


        public static void Main(string[] args)
        {
            float salary = 1000f;

            // Формула расчета налогов по стране Вьетнама.
            TaxFormulas.TaxFormula formula = TaxFormulas.GetSalaryFormula("VN");

            float tax = formula(salary);

            Console.WriteLine("Tax in Vietnam = {0}", tax);

            // Формула расчета налогов Канады
            formula = TaxFormulas.GetSalaryFormula("CA");

            tax = formula(salary);

            Console.WriteLine("Tax in Canada = {0}", tax);

            Console.Read();
        }
    }

}
Запуск примера:

4- Анонимный метод

Из Delegate вы можете создать анонимный метод (Anonymous method), который не имеет имени, только код (body) метода, это блок (block) с 0 и больше параметрами, и разные способы возвращения.
AnonymousMethod.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class AnonymousMethod
    {
        // Delegate представляет функции вида : (float) -> (float).
        // Расчет налогов основываясь на заработной плате.
        public delegate float TaxFormula(float salary);


        public static TaxFormula GetTaxFormula(string countryCode)
        {
            if ("USA" == countryCode)
            {
                TaxFormula usaFormula = delegate(float salary)
                {
                    return 10 * salary / 100;
                };
                return usaFormula;
            }
            else if ("VN" == countryCode)
            {
                TaxFormula vnFormula = delegate(float salary)
                {
                    return 5 * salary / 100;
                };
                return vnFormula;
            }
            return delegate(float salary)
            {
                return 7 * salary / 100;
            };
        }


        public static void Main(string[] args)
        {
            string countryCode = "VN";
            float salary = 1000;

            TaxFormula formula = GetTaxFormula(countryCode);

            float tax = formula(salary);

            Console.WriteLine("countryCode = {0}, salary = {1} -> tax = {2}"
                                    , countryCode, salary, tax);

            Console.Read();
        }
    }

}
Запуск:

5- Multicasting одного Delegate

C# позволяет вам добавить (+) два Delegate чтобы создать новый Delegatе. Заметьте, что добавленные Delegate должны иметь один тип функции,и в функции без возвращения. Когда выполняется новый delegate, остальные  под -delegate тоже будут выполнены. Это называется  Multicasting для delegate.
Greetings.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class Greetings
    {
        // Вид функции: (String) -> ()
        public static void Hello(String name)
        {
            Console.WriteLine("Hello " + name);
        }

        // Вид функции: (String) -> ()
        public static void Bye(string name)
        {
            Console.WriteLine("Bye " + name);
        }

        // Вид функции: (String) -> ()
        public static void Hi(string name)
        {
            Console.WriteLine("Hi " + name);
        }
    }

}
MulticastingTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDelegatesTutorial
{
    class MulticastingTest
    {
        // Объявить Delegate.
        public delegate void Greeting(string name);


        public static void Main(string[] args)
        {
            // Создать объекты Delegate.
            Greeting hello = new Greeting(Greetings.Hello);
            Greeting bye = new Greeting(Greetings.Bye);
            Greeting hi = new Greeting(Greetings.Hi);
            
            // Создать Delegate - комбинация 3 объектов выше.
            Greeting greeting = hello + bye;
           
            // Вы так же можете использовать оператор +=
            greeting += hi;

            // Выполнить greeting.
            greeting("Tom");

            Console.Read();


        }
    }

}
Запуск примера:

6- Что такое Event

В C#, Event является специальным объектом в Delegate, это место хранения методов, эти методы будут выполнены одновременно при происхождении события. Вы можете добавить выполняющие методы в объект Event объекта создающего событие.

Пример:

Класс  Button стимулирует кнопку, она означает одно Event (событие) для оповещения при нажатии (Click). Когда нажимается button, event выполняется. Вам нужно добавить методы выполнения для объекта Event снаружи.
Button.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpEventsTutorial
{
    class Button
    {

        private string label;

        public delegate void ClickHander(Button source, int x, int y);

        // Определить событие, к которому не прикреплено значение.
        // Его значение прикреплено снаружи.
        public event ClickHander OnButtonClick;

        public Button(string label)
        {
            this.label = label;
        }

        // Симуляция нажатия на этот Button (Click).
        // Определить позицию x, y на которую нажимает пользователь.
        public void Clicked()
        {
            Random random = new Random();

            // Случайное число от 1 -> 100
            int x = random.Next(1, 100);

            // Случайное число от 1 -> 20
            int y = random.Next(1, 20);


            if (OnButtonClick != null)
            {
                // Вызов обработки события.
                OnButtonClick(this, x, y);
            }
            
        }
    }


}
Класс  MyApplication стимулирует приложение с 2-мя кнопками, "Open File" и "Save File".

Вам нужно написать метод сделать что-то при нажатии пользователя в  "Open File", добавить данный метод в событие  OnButtonClick кнопки  openButton

Вам нужно написать метод сделать что-то при нажатии пользователя в  "Save File", vдобавить данный метод в событие  OnButtonClick кнопки  saveButton.
MyApplication.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpEventsTutorial
{
    class MyApplication
    {
        private Button openButton;
        private Button saveButton;
        private string fileName;

        // Симуляция приложения, имеющего Button.
        public MyApplication()
        {
            // Добавить 1 Button в интерфейс.
            this.openButton = new Button("Open File");

            // Добавить 1 Button в интерфейс.
            this.saveButton = new Button("Save File");

            // Добавить метод в событие button 'Open Button'.
            // (Свойство Multicasting у Delegate)
            this.openButton.OnButtonClick += this.OpenButtonClicked;

            // Добавить метод в событие button 'Save Button'.
            // (Свойство Multicasting у Delegate)
            this.saveButton.OnButtonClick += this.SaveButtonClicked;
        }

        private void OpenButtonClicked(Button source, int x, int y)
        {
            // Симуляция открытия окна для выбора файла чтобы открыть.
            Console.WriteLine("Open Dialog to Select a file");
            // 
            this.fileName = "File" + x + "_" + y+".txt";
            Console.WriteLine("Openning file: "+ this.fileName);
        }

        private void SaveButtonClicked(Button source, int x, int y)
        {
            if(this.fileName== null)  {
                Console.WriteLine("No file to save!");
                return;
            }
            // Save File
            Console.WriteLine("Saved file: " + this.fileName);
        }
 

        public static void Main(string[] args)
        {

            // Симуляция открытия приложения
            MyApplication myApp = new MyApplication();

            Console.WriteLine("User Click on Open Button ....");

            // Симуляция нажатия на openButton
            myApp.openButton.Clicked();

            Console.WriteLine("\n\n");
            Console.WriteLine("User Click on Save Button ....");

            // Симуляция нажатия на saveButton
            myApp.saveButton.Clicked();


            Console.Read();
            
        }
    }

}
Запуск примера: