Публикация событий, соответствующих рекомендациям .NET (руководство по программированию на C#)

Следующая процедура демонстрирует добавление событий, которые соответствуют стандартному шаблону .NET для классов и структур. Все события в библиотеке классов .NET основаны на делегате EventHandler, который определен следующим образом:

public delegate void EventHandler(object? sender, EventArgs e);

Примечание.

В .NET Framework 2.0 представлена универсальная версия этого делегата, EventHandler<TEventArgs>. В следующих примерах демонстрируется использование обеих версий.

Хотя события в определяемых классах могут быть основаны на любом допустимом типе делегата, даже на делегатах, возвращающих значение, обычно рекомендуется основывать события на шаблоне .NET с помощью EventHandler, как показано в следующем примере.

Имя EventHandler может привести к путанице, так как на самом деле этот делегат не обрабатывает событие. Делегат EventHandler и универсальный делегат EventHandler<TEventArgs> являются типами делегатов. Метод или лямбда-выражение, сигнатура которого соответствует определению делегата, является обработчиком событий и будет вызываться при возникновении события.

Публикация событий, основанных на шаблоне EventHandler

  1. (Пропустите этот шаг и перейдите к шагу 3a, если вам не нужно отправлять пользовательские данные с событием.) Объявите класс для пользовательских данных в область, который отображается как для издателя, так и для подписчиков. Затем добавьте необходимые члены для хранения пользовательских данных о событиях. В этом примере возвращается простая строка.

    public class CustomEventArgs : EventArgs
    {
        public CustomEventArgs(string message)
        {
            Message = message;
        }
    
        public string Message { get; set; }
    }
    
  2. (Пропустить этот шаг, если вы используете универсальную версию EventHandler<TEventArgs>.) Объявите делегат в классе публикации. Присвойте ему имя, которое заканчивается на EventHandler. Второй параметр указывает настраиваемый тип EventArgs.

    public delegate void CustomEventHandler(object? sender, CustomEventArgs args);
    
  3. Объявите событие в своем классе публикации, выполнив одно из следующих действий.

    1. Если у вас нет пользовательского класса EventArgs, тип события будет неуниверсальным делегатом EventHandler. Нет необходимости объявлять делегат, так как он уже объявлен в пространстве имен System, которое включается при создании проекта C#. Добавьте следующий код в класс Publisher.

      public event EventHandler? RaiseCustomEvent;
      
    2. Если вы используете неуниверсальную версию EventHandler и имеется пользовательский класс, производный от EventArgs, объявите событие внутри класса публикации и используйте делегат из шага 2 в качестве типа.

      public event CustomEventHandler? RaiseCustomEvent;
      
    3. Если используется универсальная версия, пользовательский делегат не требуется. Вместо этого в классе публикации укажите тип события как EventHandler<CustomEventArgs>, подставив имя своего класса в угловые скобки.

      public event EventHandler<CustomEventArgs>? RaiseCustomEvent;
      

Пример

В следующем примере показана вышеописанная процедура с использованием пользовательского класса EventArgs и EventHandler<TEventArgs> в качестве типа событий.

namespace DotNetEvents
{
    // Define a class to hold custom event info
    public class CustomEventArgs : EventArgs
    {
        public CustomEventArgs(string message)
        {
            Message = message;
        }

        public string Message { get; set; }
    }

    // Class that publishes an event
    class Publisher
    {
        // Declare the event using EventHandler<T>
        public event EventHandler<CustomEventArgs>? RaiseCustomEvent;

        public void DoSomething()
        {
            // Write some code that does something useful here
            // then raise the event. You can also raise an event
            // before you execute a block of code.
            OnRaiseCustomEvent(new CustomEventArgs("Event triggered"));
        }

        // Wrap event invocations inside a protected virtual method
        // to allow derived classes to override the event invocation behavior
        protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<CustomEventArgs>? raiseEvent = RaiseCustomEvent;

            // Event will be null if there are no subscribers
            if (raiseEvent != null)
            {
                // Format the string to send inside the CustomEventArgs parameter
                e.Message += $" at {DateTime.Now}";

                // Call to raise the event.
                raiseEvent(this, e);
            }
        }
    }

    //Class that subscribes to an event
    class Subscriber
    {
        private readonly string _id;

        public Subscriber(string id, Publisher pub)
        {
            _id = id;

            // Subscribe to the event
            pub.RaiseCustomEvent += HandleCustomEvent;
        }

        // Define what actions to take when the event is raised.
        void HandleCustomEvent(object? sender, CustomEventArgs e)
        {
            Console.WriteLine($"{_id} received this message: {e.Message}");
        }
    }

    class Program
    {
        static void Main()
        {
            var pub = new Publisher();
            var sub1 = new Subscriber("sub1", pub);
            var sub2 = new Subscriber("sub2", pub);

            // Call the method that raises the event.
            pub.DoSomething();

            // Keep the console window open
            Console.WriteLine("Press any key to continue...");
            Console.ReadLine();
        }
    }
}

См. также