Пользовательские наблюдаемые типы и классы поддержки


Я решил, что для некоторых частей моего проекта, общение через события было бы очень удобно, для этой цели я начал писать самые основные части системных событий, интерфейс, состоящий только из 1 EventHandler с достаточно общим названием:

public interface ICustomObservable<TArgs>
    where TArgs : EventArgs
{
    event EventHandler<TArgs> Notify;
}

Он унаследовал любой класс, который хочет сообщить кому-то о разных событиях, которые происходят.

Он может быть использован, очевидно, с любого типа, который наследует EventArgsно в настоящее время наиболее часто используется универсальный тип аргумента MediaEventArgs:

public class MediaEventArgs<TEnumeration> : EventArgs
    where TEnumeration : Enumeration<TEnumeration>
{
    public TEnumeration EventType { get; }
    public object AdditionalInfo { get; }

    public MediaEventArgs(TEnumeration eventType, object additionalInfo = null)
    {
        EventType = eventType ?? throw new ArgumentNullException(nameof(eventType), @"Enumeration cannot be null.");
        AdditionalInfo = additionalInfo;
    }
}

Пожалуйста, обратитесь к моей недавней самостоятельно отвечать за осуществление Enumeration<> тип.

Для тестирования целей следующие производные классы могут быть созданы:

public class TestEnumeration1 : Enumeration<TestEnumeration1>
{
    public static TestEnumeration1 A = new TestEnumeration1(nameof(A), 0);
    public static TestEnumeration1 B = new TestEnumeration1(nameof(B), 1);

    protected TestEnumeration1(string name, int value)
        : base(name, value)
    {
    }
}

public class TestEnumeration2 : Enumeration<TestEnumeration2>
{
    public static TestEnumeration2 C = new TestEnumeration2(nameof(C), 0);
    public static TestEnumeration2 D = new TestEnumeration2(nameof(D), 1);

    protected TestEnumeration2(string name, int value)
        : base(name, value)
    {
    }
}

public class TestCustomObservable1 : ICustomObservable<MediaEventArgs<TestEnumeration1>>
{
    public event EventHandler<MediaEventArgs<TestEnumeration1>> Notify;

    public void TestNotification(TestEnumeration1 value)
    {
        Notify?.Invoke(this, new MediaEventArgs<TestEnumeration1>(value));
    }
}

public class TestCustomObservable2 : ICustomObservable<MediaEventArgs<TestEnumeration2>>
{
    public event EventHandler<MediaEventArgs<TestEnumeration2>> Notify;

    public void TestNotification(TestEnumeration2 value)
    {
        Notify?.Invoke(this, new MediaEventArgs<TestEnumeration2>(value));
    }
}

public class TestCustomObservable3 : ICustomObservable<MediaEventArgs<TestEnumeration1>>, ICustomObservable<MediaEventArgs<TestEnumeration2>>
{
    private event EventHandler<MediaEventArgs<TestEnumeration1>> _Notify;
    event EventHandler<MediaEventArgs<TestEnumeration1>> ICustomObservable<MediaEventArgs<TestEnumeration1>>.Notify
    {
        add => _Notify += value;
        remove => _Notify -= value;
    }

    public event EventHandler<MediaEventArgs<TestEnumeration2>> Notify;

    public void Test(TestEnumeration1 value)
    {
        _Notify?.Invoke(this, new MediaEventArgs<TestEnumeration1>(value));
    }

    public void Test2(TestEnumeration2 value)
    {
        Notify?.Invoke(this, new MediaEventArgs<TestEnumeration2>(value));
    }
}

Сейчас некоторые типы требуют уведомления из нескольких источников в этом случае вы можете просто сделать:

Notifier1.Notify+=..
Notifier2.Notify+=..
Notifier3.Notify+=..

и конечно, где-то на отписку код также требуется.

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


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

internal class ObservableMap<T, TArgs> : Dictionary<T, EventHandler<TArgs>>
    where TArgs : EventArgs
{
    protected Func<ObservableMap<T, TArgs>, TArgs, T> _invokator;

    internal ObservableMap(Func<ObservableMap<T, TArgs>, TArgs, T> invokator)
        : base()
    {
        _invokator = invokator;
    }

    internal ObservableMap(IDictionary<T, EventHandler<TArgs>> values)
        : base(values)
    {
    }

    internal virtual EventHandler<TArgs> GetHandler(TArgs args)
    {
        return this.TryGetValue(_invokator.Invoke(this, args), out var handler) ? handler: null;
    }
}

internal class MediaObservableMap<TEnumeration> : ObservableMap<TEnumeration, MediaEventArgs<TEnumeration>>
    where TEnumeration : Enumeration<TEnumeration>
{
    internal MediaObservableMap(
        Func<ObservableMap<TEnumeration, MediaEventArgs<TEnumeration>>,
            MediaEventArgs<TEnumeration>, TEnumeration> invokator)
        : base(invokator)
    {
    }

    internal MediaObservableMap(IDictionary<TEnumeration, EventHandler<MediaEventArgs<TEnumeration>>> values)
        : base(values)
    {
    }

    internal EventHandler<MediaEventArgs<TEnumeration>> GetHandler(TEnumeration enumerationValue)
    {
        return TryGetValue(enumerationValue, out var handler) ? handler : null;
    }
}

Пример объявления:

var map1 = new ObservableMap<TestEnumeration1, MediaEventArgs<TestEnumeration1>>(
(map, args) => args.EventType)
{
    [TestEnumeration1.A] = (sender, args) => Item("map1 - A", sender, args),
};

var map2 = new MediaObservableMap<TestEnumeration2>((map, args) => args.EventType)
{
    [TestEnumeration2.C] = (sender, args) => Item("map2 - C", sender, args),
    [TestEnumeration2.D] = (sender, args) => Item("map2 - D", sender, args),
};

И вторая проблема решается с помощью MultipleProvidersCache класс:

internal class MultipleProvidersCache
{
    protected readonly ProviderActionCache<EventHandler<object>> _notificationsCache =
        new ProviderActionCache<EventHandler<object>>();

    protected readonly ProviderActionCache<Action> _providerUnsubscriberCache = new ProviderActionCache<Action>();

    internal virtual void AddProvider<T, TArgs>(ICustomObservable<TArgs> provider, ObservableMap<T, TArgs> map)
        where TArgs : EventArgs
    {
        var providerType = provider.GetType();
        _notificationsCache.Add<TArgs>(providerType, (sender, args) =>
        {
            var unboxedArgs = (TArgs) args;
            map.GetHandler(unboxedArgs)?.Invoke(sender, unboxedArgs);
        });
        void NotifierDelegate(object sender, TArgs args) => Provider_OnNotify(sender, args, providerType);
        provider.Notify += NotifierDelegate;
        _providerUnsubscriberCache.Add<TArgs>(providerType, () => provider.Notify -= NotifierDelegate);
    }

    internal virtual bool RemoveProvider<TProvider, TArgs>()
        where TArgs : EventArgs
        where TProvider : ICustomObservable<TArgs>
    {
        return RemoveProviderImpl<TArgs>(typeof(TProvider));
    }

    protected virtual bool RemoveProviderImpl<TArgs>(Type providerType)
        where TArgs : EventArgs
    {
        if (_notificationsCache.RemoveAction<TArgs>(providerType))
        {
            _providerUnsubscriberCache.GetAction<TArgs>(providerType).Invoke();
            _providerUnsubscriberCache.RemoveAction<TArgs>(providerType);
            return true;
        }
        return false;
    }

    protected virtual void Provider_OnNotify<TArgs>(object sender, TArgs e, Type providerType)
        where TArgs : EventArgs
    {
        if (_notificationsCache.TryGetAction<TArgs>(providerType, out var action))
        {
            action.Invoke(sender, e);
        }
    }

    protected sealed class ProviderActionCache<TAction> : IEnumerable<KeyValuePair<Type, Dictionary<Type, TAction>>>
    {
        private readonly IDictionary<Type, Dictionary<Type, TAction>> _dictionary;

        internal ProviderActionCache()
        {
            _dictionary = new Dictionary<Type, Dictionary<Type, TAction>>();
        }

        internal void Add<TArgs>(Type key, TAction value)
            where TArgs : EventArgs
        {
            if (_dictionary.TryGetValue(key, out var values))
            {
                values.Add(typeof(TArgs), value);
            }
            else
            {
                _dictionary.Add(key, new Dictionary<Type, TAction> {[typeof(TArgs)] = value});
            }
        }

        internal bool RemoveProvider(Type providerType)
        {
            return _dictionary.Remove(providerType);
        }

        internal bool RemoveAction<TArgs>(Type providerType)
            where TArgs : EventArgs
        {
            return _dictionary.TryGetValue(providerType, out var values) && values.Remove(typeof(TArgs));
        }

        internal bool TryGetProvider(Type providerType, out Dictionary<Type, TAction> values)
        {
            return _dictionary.TryGetValue(providerType, out values);
        }

        internal bool TryGetAction<TArgs>(Type providerType, out TAction action)
            where TArgs : EventArgs
        {
            if (TryGetProvider(providerType, out var value))
            {
                action = value[typeof(TArgs)];
                return true;
            }
            action = default(TAction);
            return false;
        }

        internal TAction GetAction<TArgs>(Type providerType)
            where TArgs : EventArgs
        {
            return _dictionary[providerType][typeof(TArgs)];
        }

        internal Dictionary<Type, TAction> GetProvider(Type providerType)
        {
            return _dictionary[providerType];
        }

        #region Implementation of IEnumerable

        public IEnumerator<KeyValuePair<Type, Dictionary<Type, TAction>>> GetEnumerator()
        {
            return _dictionary.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }
}

Для случаев, когда класс реализует ICustomObservable<> только 1 раз, это довольно прямо вперед, но если класс реализует интерфейс несколько раз MultipleProvidersCache бы внутренне относиться к нему, как если бы это были несколько разных типов все они объединены в рамках того же класса, что означает, что вы не можете иметь несколько ObservableMap<>на один и тот же поставщик.

Следующие аварии:

TestCustomObservable3 tco3 = new TestCustomObservable3();
MultipleProvidersCache mpc = new MultipleProvidersCache();
mpc.AddProvider(tco3, map1);
mpc.AddProvider(tco3, map1);

Но не этого:

TestCustomObservable3 tco3 = new TestCustomObservable3();
MultipleProvidersCache mpc = new MultipleProvidersCache();
mpc.AddProvider(tco3, map1);
mpc.AddProvider(tco3, map2);

Пример использования

MultipleProvidersCache mpc = new MultipleProvidersCache();
TestCustomObservable1 tco1 = new TestCustomObservable1();
TestCustomObservable2 tco2 = new TestCustomObservable2();
TestCustomObservable3 tco3 = new TestCustomObservable3();

var map1 = new ObservableMap<TestEnumeration1, MediaEventArgs<TestEnumeration1>>(
(map, args) => args.EventType)
{
    [TestEnumeration1.A] = (sender, args) => Item("map1 - A", sender, args),
};

var map2 = new MediaObservableMap<TestEnumeration2>((map, args) => args.EventType)
{
    [TestEnumeration2.C] = (sender, args) => Item("map2 - C", sender, args),
    [TestEnumeration2.D] = (sender, args) => Item("map2 - D", sender, args),
};

mpc.AddProvider(tco1, map1);
mpc.AddProvider(tco2, map2);
mpc.AddProvider(tco3, map2);
mpc.AddProvider(tco3, map1);

tco1.TestNotification(TestEnumeration1.A);
tco2.TestNotification(TestEnumeration2.D);
mpc.RemoveProvider<TestCustomObservable2, MediaEventArgs<TestEnumeration2>>();
tco1.TestNotification(TestEnumeration1.A);
tco1.TestNotification(TestEnumeration1.B);
tco2.TestNotification(TestEnumeration2.C);
tco3.Test(TestEnumeration1.B);
tco3.Test(TestEnumeration1.A);
mpc.RemoveProvider<TestCustomObservable3, MediaEventArgs<TestEnumeration1>>();
tco3.Test2(TestEnumeration2.D);
tco3.Test(TestEnumeration1.A);

private static void Item<TEnumeration>(string value, object sender, MediaEventArgs<TEnumeration> args)
    where TEnumeration : Enumeration<TEnumeration>
{
    Console.WriteLine($"{value}{Environment.NewLine}" +
                        $"Sender = {sender}{Environment.NewLine}" +
                        $"EventType = {args.EventType}");
}

Это должно приводить к следующему результату:

map1 - A
Sender = CodeReviewCSharp7.TestCustomObservable1
EventType = A
map2 - D
Sender = CodeReviewCSharp7.TestCustomObservable2
EventType = D
map1 - A
Sender = CodeReviewCSharp7.TestCustomObservable1
EventType = A
map1 - A
Sender = CodeReviewCSharp7.TestCustomObservable3
EventType = A
map2 - D
Sender = CodeReviewCSharp7.TestCustomObservable3
EventType = D

Любая критика и предложения приветствуются.



Комментарии
1 ответ

Может быть, я неправильно понял ваши намерения, но MultipleProvidersCache выглядит как чрезмерно сложной (АПИ-мудрый) событие агрегатор (ака сообщение хаб) реализации. Хотя различные реализации имеют разные вкусы, как правило, при написании одного, нужно постараться отпустить event ключевое слово. Например, самые элементарные API может выглядеть следующим образом:

interface IEventAggregator
{
IDisposable Subsribe<TMessage>(Action<TMessage> action);
void Publish<TMessage>(TMessage message);
}

Здесь TMessage действует как EventArgs замена. Идея заключается в том, что любой объект может подписаться на "события" по телефону Subsribe способ и любой объект может вызывать "события" по телефону Publish. IEventAggregator выступает в качестве посредника и передает сообщения от издателей в список подписанных ActionС.

P. S. Обратите внимание, что некоторые часто используемые базы (например, призмы, MvvmLight, и большинство других в MVVM фреймворков основе) тебя прикрою и уже есть какая-то система обмена сообщениями на месте. NuGet, который имеет несколько отдельных реализаций, а также, если вы работаете, скажем, проект единства. Однако реализация собственных событий агрегатор-это большие физические нагрузки (у меня вообще общая моя реализация на Си, когда я играл вокруг с потока данных).

3
ответ дан 4 апреля 2018 в 08:04 Источник Поделиться