Как отслеживать подписки на различные события - PullRequest
0 голосов
/ 10 марта 2019

Рассмотрим пример ниже.Этот пример имеет простой источник события (Ticker) и форму, которая подписывается на его событие (Ticked) через делегата, который обновляет заголовок формы.В этом примере для простоты есть только одно событие, но рассмотрим случай, когда существует множество событий, на которые подписываются несколько форм.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
    class Program
    {
        // event cannot be used as a key type
        // Dictionary<event, List<EventHandler>> subscriptions = new Dictionary<event, List<EventHandler>>();

        static void Main(string[] args)
        {
            Ticker ticker = new Ticker();
            Form form = new Form();
            form.Show();
            EventHandler eventHandler = new EventHandler((s, e) => {
                form.Invoke(new Action(() => { form.Text = DateTime.Now.ToString(); }));
            });
            // save a reference to the event and the delegate to be added
            //if (!subscriptions.ContainsKey(ticker.Ticked))
            //    subscriptions.Add(ticker.Ticked, new List<EventHandler>());
            //subscriptions[ticker.Ticked].Add(eventHandler);
            //form.FormClosing += (s, e) => {
            //    foreach (KeyValuePair<event, List<EventHandler>> subscription in subscriptions)
            //        foreach (EventHandler eventHandler in subscription.Value)
            //            subscription.Key -= eventHandler;
            //};
            //finally subscribe to the event(s)
            ticker.Ticked += eventHandler;
            Application.Run();
        }

        class Ticker
        {
            public event EventHandler Ticked;

            public Ticker()
            {
                new Thread(new ThreadStart(() => {
                    while (true)
                    {
                        Ticked?.Invoke(null, null);
                        Thread.Sleep(1000);
                    }
                })).Start();
            }
        }
    }
}

Как сохранить в коллекции события, подписанные формой, и делегатыдобавлены к каждому событию формой, чтобы их можно было удалить перед закрытием формы?

1 Ответ

1 голос
/ 10 марта 2019

Краткий ответ: это невозможно.Вы не можете хранить события в коллекциях и позже очищать их списки обработчиков.

События служат только одной цели - инкапсуляции.Единственное, что они делают, это предоставляют методы доступа (а именно add и remove), поэтому код вне вашего класса может только добавлять / удалять обработчики только в поле делегата поддержки.Эти фрагменты кода в основном одинаковы:

class MyClass
{
    public event EventHandler MyEvent;
}
class MyClass
{
    private EventHandler myDelegate;

    public event EventHandler MyEvent
    {
        add => myDelegate += value;
        remove => myDelegate -= value;
    }
}

Но предположим, что мы не используем события, а напрямую используем делегаты.Вы можете создать словарь, в котором ключи являются делегатами, а не событиями, но это не поможет вашей проблеме.Это потому, что делегаты неизменны .Вы не можете сохранить делегата в коллекции, а затем извлечь его и очистить его список вызовов.

Единственное решение здесь будет заключаться в прямой ссылке на каждое событие, как в этом коде.Я не уверен, подойдет ли вам это решение.

using System;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
    class Program
    {
        static void Main()
        {
            var ticker = new Ticker();
            var form = new Form();
            form.Show();

            form.FormClosing += (s, e) => ticker.ClearSubscriptions();

            ticker.Ticked += new EventHandler((s, e) => form.Invoke(
                new Action(() => form.Text = DateTime.Now.ToString())));

            Application.Run();
        }

        class Ticker
        {
            public event EventHandler Ticked;

            public Ticker()
            {
                new Thread(new ThreadStart(() => {
                    while (true)
                    {
                        Ticked?.Invoke(this, EventArgs.Empty);
                        Thread.Sleep(1000);
                    }
                })).Start();
            }

            public void ClearSubscriptions()
            {
                Ticked = null;
            }
        }
    }
}

Как видите, ClearSubscriptions очищает событие Ticked вручную.Если у вас есть больше событий, вы также должны очистить их вручную и только в классе Ticker, потому что это единственное место, которое имеет доступ к базовому делегату.Вы можете очистить только те события, которые объявили сами.

Кроме того, вы можете сохранить отдельный список для каждого события.

static void Main()
{
    var ticker = new Ticker();
    var form = new Form();
    form.Show();

    var tickedSubscriptions = new List<EventHandler>();

    form.FormClosing += (s, e) =>
    {
        foreach (var subscription in tickedSubscriptions)
        {
            ticker.Ticked -= subscription;
        }

        tickedSubscriptions.Clear();
    };

    var handler = new EventHandler((s, e) => form.Invoke(
        new Action(() => form.Text = DateTime.Now.ToString())));

    tickedSubscriptions.Add(handler);
    ticker.Ticked += handler;

    Application.Run();
}

Но, на мой взгляд, это решение далеко не идеальнопотому что вам нужно отслеживать множество отдельных списков.

ОБНОВЛЕНИЕ:

Я подумал о другом решении, которое подходит для вашего случая.Хотя я не уверен, элегантно ли это.

Несмотря на то, что делегаты являются неизменяемыми, ничто не мешает нам создавать объекты-оболочки, которые могут изменить вспомогательного делегата, и поместить эти оболочки в словарь.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;

namespace ConsoleApp3
{
    class Program
    {
        private static Dictionary<EventHandlerWrapper, List<EventHandler>> subscriptions =
        new Dictionary<EventHandlerWrapper, List<EventHandler>>();

        static void Main()
        {
            var ticker = new Ticker();
            var form = new Form();
            form.Show();

            form.FormClosing += (s, e) =>
            {
                foreach (var subscription in subscriptions)
                {
                    foreach (var handler in subscription.Value)
                    {
                        subscription.Key.Remove(handler);
                    }
                }

                subscriptions.Clear();
            };

            var updateTitle = new EventHandler((s, e) =>
                form.Invoke(new Action(() => form.Text = DateTime.Now.ToString())));

            ticker.Ticked += updateTitle;
            subscriptions.Add(ticker.TickedWrapper, new List<EventHandler> { updateTitle });

            Application.Run();
        }

        class Ticker
        {
            public event EventHandler Ticked;
            public EventHandlerWrapper TickedWrapper;

            public Ticker()
            {
                TickedWrapper = new EventHandlerWrapper(
                    () => Ticked,
                    handler => Ticked += handler,
                    handler => Ticked -= handler);

                new Thread(new ThreadStart(() => {
                    while (true)
                    {
                        Ticked?.Invoke(this, EventArgs.Empty);
                        Thread.Sleep(1000);
                    }
                })).Start();
            }
        }

        class EventHandlerWrapper
        {
            public Func<EventHandler> Get { get; }
            public Action<EventHandler> Add { get; }
            public Action<EventHandler> Remove { get; }

            public EventHandlerWrapper(
                Func<EventHandler> get,
                Action<EventHandler> add,
                Action<EventHandler> remove)
            {
                this.Get = get;
                this.Add = add;
                this.Remove = remove;
            }
        }
    }
}
...