Custom observable types and their supporting classes

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
1
down vote

favorite












I decided that for some parts of my project, communication through events would be very handy, for that purpose I started writing the most basic part of the event system, an interface consisting of only 1 EventHandler with rather generic name:



public interface ICustomObservable<TArgs>
where TArgs : EventArgs

event EventHandler<TArgs> Notify;



It's being inherited by any class that wishes to notify someone about different events that are occurring.



It can be used obviously with any type that inherits EventArgs, but currently the most often used generic type argument is 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;




Please, refer to my recent self-answer for the implementation of Enumeration<> type.



For testing purposes the following derived classes can be created:



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));





Now some types require notifications from multiple sources for this case you can simply do:



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


and of course somewhere down the code unsubscription is also required.



It works but it doesn't look nice and since most of the event handlers for the Notify event require an extra dictionary to test against each Enumeration type, we end up with long dictionary declarations, very repetitive and mostly 1 liner methods, I decide to introduce few helper classes to ease the job of the consumer.




Solving the long dictionary declarations problem is pretty easy. Few wrapper classes and it's basically done:



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;




Example declarations:



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),
;



And the second problem is solved using the help of the MultipleProvidersCache class:



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




For the cases when a class implements ICustomObservable<> only 1 time, it's pretty straight forward, but if a class implements the interface multiple times,
MultipleProvidersCache would internally treat it as if it were multiple separate types all unified under the same class type, which means you cannot have multiple ObservableMap<>s on the same provider.



The following should crash:



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


But not this:



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


Example usage



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($"valueEnvironment.NewLine" +
$"Sender = senderEnvironment.NewLine" +
$"EventType = args.EventType");



This should produce the following output:



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


Any critique or suggestions are welcome.







share|improve this question















  • 1




    Feeling like reinventing the observer pattern? ;-)
    – t3chb0t
    Apr 4 at 6:21
















up vote
1
down vote

favorite












I decided that for some parts of my project, communication through events would be very handy, for that purpose I started writing the most basic part of the event system, an interface consisting of only 1 EventHandler with rather generic name:



public interface ICustomObservable<TArgs>
where TArgs : EventArgs

event EventHandler<TArgs> Notify;



It's being inherited by any class that wishes to notify someone about different events that are occurring.



It can be used obviously with any type that inherits EventArgs, but currently the most often used generic type argument is 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;




Please, refer to my recent self-answer for the implementation of Enumeration<> type.



For testing purposes the following derived classes can be created:



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));





Now some types require notifications from multiple sources for this case you can simply do:



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


and of course somewhere down the code unsubscription is also required.



It works but it doesn't look nice and since most of the event handlers for the Notify event require an extra dictionary to test against each Enumeration type, we end up with long dictionary declarations, very repetitive and mostly 1 liner methods, I decide to introduce few helper classes to ease the job of the consumer.




Solving the long dictionary declarations problem is pretty easy. Few wrapper classes and it's basically done:



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;




Example declarations:



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),
;



And the second problem is solved using the help of the MultipleProvidersCache class:



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




For the cases when a class implements ICustomObservable<> only 1 time, it's pretty straight forward, but if a class implements the interface multiple times,
MultipleProvidersCache would internally treat it as if it were multiple separate types all unified under the same class type, which means you cannot have multiple ObservableMap<>s on the same provider.



The following should crash:



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


But not this:



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


Example usage



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($"valueEnvironment.NewLine" +
$"Sender = senderEnvironment.NewLine" +
$"EventType = args.EventType");



This should produce the following output:



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


Any critique or suggestions are welcome.







share|improve this question















  • 1




    Feeling like reinventing the observer pattern? ;-)
    – t3chb0t
    Apr 4 at 6:21












up vote
1
down vote

favorite









up vote
1
down vote

favorite











I decided that for some parts of my project, communication through events would be very handy, for that purpose I started writing the most basic part of the event system, an interface consisting of only 1 EventHandler with rather generic name:



public interface ICustomObservable<TArgs>
where TArgs : EventArgs

event EventHandler<TArgs> Notify;



It's being inherited by any class that wishes to notify someone about different events that are occurring.



It can be used obviously with any type that inherits EventArgs, but currently the most often used generic type argument is 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;




Please, refer to my recent self-answer for the implementation of Enumeration<> type.



For testing purposes the following derived classes can be created:



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));





Now some types require notifications from multiple sources for this case you can simply do:



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


and of course somewhere down the code unsubscription is also required.



It works but it doesn't look nice and since most of the event handlers for the Notify event require an extra dictionary to test against each Enumeration type, we end up with long dictionary declarations, very repetitive and mostly 1 liner methods, I decide to introduce few helper classes to ease the job of the consumer.




Solving the long dictionary declarations problem is pretty easy. Few wrapper classes and it's basically done:



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;




Example declarations:



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),
;



And the second problem is solved using the help of the MultipleProvidersCache class:



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




For the cases when a class implements ICustomObservable<> only 1 time, it's pretty straight forward, but if a class implements the interface multiple times,
MultipleProvidersCache would internally treat it as if it were multiple separate types all unified under the same class type, which means you cannot have multiple ObservableMap<>s on the same provider.



The following should crash:



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


But not this:



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


Example usage



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($"valueEnvironment.NewLine" +
$"Sender = senderEnvironment.NewLine" +
$"EventType = args.EventType");



This should produce the following output:



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


Any critique or suggestions are welcome.







share|improve this question











I decided that for some parts of my project, communication through events would be very handy, for that purpose I started writing the most basic part of the event system, an interface consisting of only 1 EventHandler with rather generic name:



public interface ICustomObservable<TArgs>
where TArgs : EventArgs

event EventHandler<TArgs> Notify;



It's being inherited by any class that wishes to notify someone about different events that are occurring.



It can be used obviously with any type that inherits EventArgs, but currently the most often used generic type argument is 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;




Please, refer to my recent self-answer for the implementation of Enumeration<> type.



For testing purposes the following derived classes can be created:



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));





Now some types require notifications from multiple sources for this case you can simply do:



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


and of course somewhere down the code unsubscription is also required.



It works but it doesn't look nice and since most of the event handlers for the Notify event require an extra dictionary to test against each Enumeration type, we end up with long dictionary declarations, very repetitive and mostly 1 liner methods, I decide to introduce few helper classes to ease the job of the consumer.




Solving the long dictionary declarations problem is pretty easy. Few wrapper classes and it's basically done:



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;




Example declarations:



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),
;



And the second problem is solved using the help of the MultipleProvidersCache class:



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




For the cases when a class implements ICustomObservable<> only 1 time, it's pretty straight forward, but if a class implements the interface multiple times,
MultipleProvidersCache would internally treat it as if it were multiple separate types all unified under the same class type, which means you cannot have multiple ObservableMap<>s on the same provider.



The following should crash:



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


But not this:



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


Example usage



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($"valueEnvironment.NewLine" +
$"Sender = senderEnvironment.NewLine" +
$"EventType = args.EventType");



This should produce the following output:



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


Any critique or suggestions are welcome.









share|improve this question










share|improve this question




share|improve this question









asked Apr 3 at 21:48









Denis

6,16021453




6,16021453







  • 1




    Feeling like reinventing the observer pattern? ;-)
    – t3chb0t
    Apr 4 at 6:21












  • 1




    Feeling like reinventing the observer pattern? ;-)
    – t3chb0t
    Apr 4 at 6:21







1




1




Feeling like reinventing the observer pattern? ;-)
– t3chb0t
Apr 4 at 6:21




Feeling like reinventing the observer pattern? ;-)
– t3chb0t
Apr 4 at 6:21










1 Answer
1






active

oldest

votes

















up vote
3
down vote













Maybe I misunderstood your intent, but MultipleProvidersCache looks a lot like an overly complex (API-wise) event aggregator (aka message hub) implementation. Even though different implementations have different flavors, normally when writing one, you should try to let go of event keyword. For example, the most basic API can look like this:



interface IEventAggregator

IDisposable Subsribe<TMessage>(Action<TMessage> action);
void Publish<TMessage>(TMessage message);



Here TMessage acts as EventArgs replacement. The idea is that any object can subscribe to "events" by calling Subsribe method and any object can invoke "events" by calling Publish. IEventAggregator acts as a mediator and passes messages from publishers to the list of subscribed Actions.



P.S. Note, that some commonly used frameworks (e.g. Prism, MvvmLight, and most other MVVM based frameworks) got you covered and already have some messaging system in place. Nuget has multiple standalone implementations available as well, if you are working on, say, Unity project. However implementing your own event aggregator is a great exercise (I have actually shared my implementation on SR, when I was playing around with Dataflow).






share|improve this answer























  • This looks very familiar... almost like the Subject from the reactive extensions that combines both the IObserver and the IObservable interfaces.
    – t3chb0t
    Apr 4 at 8:23











  • The point is that whenever Notifier X fires his Notify event, if X has already been mapped in MultipleProvidersCache along with an ObservableMap, only the map associated with this specific X notifier should know that an event has occurred. API-wise the methods are being invoked in fairly simple way. I have used a very similar interface to this one except that I still had EventArgs there, in a different situation where I don't mind sending notifications to all subscribers, instead of just a selected few(1).
    – Denis
    Apr 4 at 14:19











  • @Denis the idea is, that you always publish a strongly typed message. So every message is only sent to subscribers that can handle message of that type. If you want to send a notifications to some subscribers but not to other subscribers, you just need to create two messages of different type.
    – Nikita B
    Apr 4 at 14:36










  • @Denis with this approach you can easily "find usages" of any given message to quickly lookup both publishers and subscribers. And that's where I feel like your implementation comes short. Looking at your code it is really hard to figure out, which class handles which event under which circumstances.
    – Nikita B
    Apr 4 at 14:39










  • I think it will be almost the same thing, the reason the current lookup looks complicated at first glance, is because you can have different providers that use the same TMessage in which case, I dont want every notifier's map that uses TMessage type to get invoked but only the one that the user requested at the start with AddProvider(provider1, map1) , I think you also have that problem, which to me seems like it wouldn't require a lot less work. If you have better idea of how this will work, can you please provide an example usage? :)
    – Denis
    Apr 4 at 14:51










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191202%2fcustom-observable-types-and-their-supporting-classes%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
3
down vote













Maybe I misunderstood your intent, but MultipleProvidersCache looks a lot like an overly complex (API-wise) event aggregator (aka message hub) implementation. Even though different implementations have different flavors, normally when writing one, you should try to let go of event keyword. For example, the most basic API can look like this:



interface IEventAggregator

IDisposable Subsribe<TMessage>(Action<TMessage> action);
void Publish<TMessage>(TMessage message);



Here TMessage acts as EventArgs replacement. The idea is that any object can subscribe to "events" by calling Subsribe method and any object can invoke "events" by calling Publish. IEventAggregator acts as a mediator and passes messages from publishers to the list of subscribed Actions.



P.S. Note, that some commonly used frameworks (e.g. Prism, MvvmLight, and most other MVVM based frameworks) got you covered and already have some messaging system in place. Nuget has multiple standalone implementations available as well, if you are working on, say, Unity project. However implementing your own event aggregator is a great exercise (I have actually shared my implementation on SR, when I was playing around with Dataflow).






share|improve this answer























  • This looks very familiar... almost like the Subject from the reactive extensions that combines both the IObserver and the IObservable interfaces.
    – t3chb0t
    Apr 4 at 8:23











  • The point is that whenever Notifier X fires his Notify event, if X has already been mapped in MultipleProvidersCache along with an ObservableMap, only the map associated with this specific X notifier should know that an event has occurred. API-wise the methods are being invoked in fairly simple way. I have used a very similar interface to this one except that I still had EventArgs there, in a different situation where I don't mind sending notifications to all subscribers, instead of just a selected few(1).
    – Denis
    Apr 4 at 14:19











  • @Denis the idea is, that you always publish a strongly typed message. So every message is only sent to subscribers that can handle message of that type. If you want to send a notifications to some subscribers but not to other subscribers, you just need to create two messages of different type.
    – Nikita B
    Apr 4 at 14:36










  • @Denis with this approach you can easily "find usages" of any given message to quickly lookup both publishers and subscribers. And that's where I feel like your implementation comes short. Looking at your code it is really hard to figure out, which class handles which event under which circumstances.
    – Nikita B
    Apr 4 at 14:39










  • I think it will be almost the same thing, the reason the current lookup looks complicated at first glance, is because you can have different providers that use the same TMessage in which case, I dont want every notifier's map that uses TMessage type to get invoked but only the one that the user requested at the start with AddProvider(provider1, map1) , I think you also have that problem, which to me seems like it wouldn't require a lot less work. If you have better idea of how this will work, can you please provide an example usage? :)
    – Denis
    Apr 4 at 14:51














up vote
3
down vote













Maybe I misunderstood your intent, but MultipleProvidersCache looks a lot like an overly complex (API-wise) event aggregator (aka message hub) implementation. Even though different implementations have different flavors, normally when writing one, you should try to let go of event keyword. For example, the most basic API can look like this:



interface IEventAggregator

IDisposable Subsribe<TMessage>(Action<TMessage> action);
void Publish<TMessage>(TMessage message);



Here TMessage acts as EventArgs replacement. The idea is that any object can subscribe to "events" by calling Subsribe method and any object can invoke "events" by calling Publish. IEventAggregator acts as a mediator and passes messages from publishers to the list of subscribed Actions.



P.S. Note, that some commonly used frameworks (e.g. Prism, MvvmLight, and most other MVVM based frameworks) got you covered and already have some messaging system in place. Nuget has multiple standalone implementations available as well, if you are working on, say, Unity project. However implementing your own event aggregator is a great exercise (I have actually shared my implementation on SR, when I was playing around with Dataflow).






share|improve this answer























  • This looks very familiar... almost like the Subject from the reactive extensions that combines both the IObserver and the IObservable interfaces.
    – t3chb0t
    Apr 4 at 8:23











  • The point is that whenever Notifier X fires his Notify event, if X has already been mapped in MultipleProvidersCache along with an ObservableMap, only the map associated with this specific X notifier should know that an event has occurred. API-wise the methods are being invoked in fairly simple way. I have used a very similar interface to this one except that I still had EventArgs there, in a different situation where I don't mind sending notifications to all subscribers, instead of just a selected few(1).
    – Denis
    Apr 4 at 14:19











  • @Denis the idea is, that you always publish a strongly typed message. So every message is only sent to subscribers that can handle message of that type. If you want to send a notifications to some subscribers but not to other subscribers, you just need to create two messages of different type.
    – Nikita B
    Apr 4 at 14:36










  • @Denis with this approach you can easily "find usages" of any given message to quickly lookup both publishers and subscribers. And that's where I feel like your implementation comes short. Looking at your code it is really hard to figure out, which class handles which event under which circumstances.
    – Nikita B
    Apr 4 at 14:39










  • I think it will be almost the same thing, the reason the current lookup looks complicated at first glance, is because you can have different providers that use the same TMessage in which case, I dont want every notifier's map that uses TMessage type to get invoked but only the one that the user requested at the start with AddProvider(provider1, map1) , I think you also have that problem, which to me seems like it wouldn't require a lot less work. If you have better idea of how this will work, can you please provide an example usage? :)
    – Denis
    Apr 4 at 14:51












up vote
3
down vote










up vote
3
down vote









Maybe I misunderstood your intent, but MultipleProvidersCache looks a lot like an overly complex (API-wise) event aggregator (aka message hub) implementation. Even though different implementations have different flavors, normally when writing one, you should try to let go of event keyword. For example, the most basic API can look like this:



interface IEventAggregator

IDisposable Subsribe<TMessage>(Action<TMessage> action);
void Publish<TMessage>(TMessage message);



Here TMessage acts as EventArgs replacement. The idea is that any object can subscribe to "events" by calling Subsribe method and any object can invoke "events" by calling Publish. IEventAggregator acts as a mediator and passes messages from publishers to the list of subscribed Actions.



P.S. Note, that some commonly used frameworks (e.g. Prism, MvvmLight, and most other MVVM based frameworks) got you covered and already have some messaging system in place. Nuget has multiple standalone implementations available as well, if you are working on, say, Unity project. However implementing your own event aggregator is a great exercise (I have actually shared my implementation on SR, when I was playing around with Dataflow).






share|improve this answer















Maybe I misunderstood your intent, but MultipleProvidersCache looks a lot like an overly complex (API-wise) event aggregator (aka message hub) implementation. Even though different implementations have different flavors, normally when writing one, you should try to let go of event keyword. For example, the most basic API can look like this:



interface IEventAggregator

IDisposable Subsribe<TMessage>(Action<TMessage> action);
void Publish<TMessage>(TMessage message);



Here TMessage acts as EventArgs replacement. The idea is that any object can subscribe to "events" by calling Subsribe method and any object can invoke "events" by calling Publish. IEventAggregator acts as a mediator and passes messages from publishers to the list of subscribed Actions.



P.S. Note, that some commonly used frameworks (e.g. Prism, MvvmLight, and most other MVVM based frameworks) got you covered and already have some messaging system in place. Nuget has multiple standalone implementations available as well, if you are working on, say, Unity project. However implementing your own event aggregator is a great exercise (I have actually shared my implementation on SR, when I was playing around with Dataflow).







share|improve this answer















share|improve this answer



share|improve this answer








edited Apr 4 at 14:19


























answered Apr 4 at 8:12









Nikita B

12.3k11652




12.3k11652











  • This looks very familiar... almost like the Subject from the reactive extensions that combines both the IObserver and the IObservable interfaces.
    – t3chb0t
    Apr 4 at 8:23











  • The point is that whenever Notifier X fires his Notify event, if X has already been mapped in MultipleProvidersCache along with an ObservableMap, only the map associated with this specific X notifier should know that an event has occurred. API-wise the methods are being invoked in fairly simple way. I have used a very similar interface to this one except that I still had EventArgs there, in a different situation where I don't mind sending notifications to all subscribers, instead of just a selected few(1).
    – Denis
    Apr 4 at 14:19











  • @Denis the idea is, that you always publish a strongly typed message. So every message is only sent to subscribers that can handle message of that type. If you want to send a notifications to some subscribers but not to other subscribers, you just need to create two messages of different type.
    – Nikita B
    Apr 4 at 14:36










  • @Denis with this approach you can easily "find usages" of any given message to quickly lookup both publishers and subscribers. And that's where I feel like your implementation comes short. Looking at your code it is really hard to figure out, which class handles which event under which circumstances.
    – Nikita B
    Apr 4 at 14:39










  • I think it will be almost the same thing, the reason the current lookup looks complicated at first glance, is because you can have different providers that use the same TMessage in which case, I dont want every notifier's map that uses TMessage type to get invoked but only the one that the user requested at the start with AddProvider(provider1, map1) , I think you also have that problem, which to me seems like it wouldn't require a lot less work. If you have better idea of how this will work, can you please provide an example usage? :)
    – Denis
    Apr 4 at 14:51
















  • This looks very familiar... almost like the Subject from the reactive extensions that combines both the IObserver and the IObservable interfaces.
    – t3chb0t
    Apr 4 at 8:23











  • The point is that whenever Notifier X fires his Notify event, if X has already been mapped in MultipleProvidersCache along with an ObservableMap, only the map associated with this specific X notifier should know that an event has occurred. API-wise the methods are being invoked in fairly simple way. I have used a very similar interface to this one except that I still had EventArgs there, in a different situation where I don't mind sending notifications to all subscribers, instead of just a selected few(1).
    – Denis
    Apr 4 at 14:19











  • @Denis the idea is, that you always publish a strongly typed message. So every message is only sent to subscribers that can handle message of that type. If you want to send a notifications to some subscribers but not to other subscribers, you just need to create two messages of different type.
    – Nikita B
    Apr 4 at 14:36










  • @Denis with this approach you can easily "find usages" of any given message to quickly lookup both publishers and subscribers. And that's where I feel like your implementation comes short. Looking at your code it is really hard to figure out, which class handles which event under which circumstances.
    – Nikita B
    Apr 4 at 14:39










  • I think it will be almost the same thing, the reason the current lookup looks complicated at first glance, is because you can have different providers that use the same TMessage in which case, I dont want every notifier's map that uses TMessage type to get invoked but only the one that the user requested at the start with AddProvider(provider1, map1) , I think you also have that problem, which to me seems like it wouldn't require a lot less work. If you have better idea of how this will work, can you please provide an example usage? :)
    – Denis
    Apr 4 at 14:51















This looks very familiar... almost like the Subject from the reactive extensions that combines both the IObserver and the IObservable interfaces.
– t3chb0t
Apr 4 at 8:23





This looks very familiar... almost like the Subject from the reactive extensions that combines both the IObserver and the IObservable interfaces.
– t3chb0t
Apr 4 at 8:23













The point is that whenever Notifier X fires his Notify event, if X has already been mapped in MultipleProvidersCache along with an ObservableMap, only the map associated with this specific X notifier should know that an event has occurred. API-wise the methods are being invoked in fairly simple way. I have used a very similar interface to this one except that I still had EventArgs there, in a different situation where I don't mind sending notifications to all subscribers, instead of just a selected few(1).
– Denis
Apr 4 at 14:19





The point is that whenever Notifier X fires his Notify event, if X has already been mapped in MultipleProvidersCache along with an ObservableMap, only the map associated with this specific X notifier should know that an event has occurred. API-wise the methods are being invoked in fairly simple way. I have used a very similar interface to this one except that I still had EventArgs there, in a different situation where I don't mind sending notifications to all subscribers, instead of just a selected few(1).
– Denis
Apr 4 at 14:19













@Denis the idea is, that you always publish a strongly typed message. So every message is only sent to subscribers that can handle message of that type. If you want to send a notifications to some subscribers but not to other subscribers, you just need to create two messages of different type.
– Nikita B
Apr 4 at 14:36




@Denis the idea is, that you always publish a strongly typed message. So every message is only sent to subscribers that can handle message of that type. If you want to send a notifications to some subscribers but not to other subscribers, you just need to create two messages of different type.
– Nikita B
Apr 4 at 14:36












@Denis with this approach you can easily "find usages" of any given message to quickly lookup both publishers and subscribers. And that's where I feel like your implementation comes short. Looking at your code it is really hard to figure out, which class handles which event under which circumstances.
– Nikita B
Apr 4 at 14:39




@Denis with this approach you can easily "find usages" of any given message to quickly lookup both publishers and subscribers. And that's where I feel like your implementation comes short. Looking at your code it is really hard to figure out, which class handles which event under which circumstances.
– Nikita B
Apr 4 at 14:39












I think it will be almost the same thing, the reason the current lookup looks complicated at first glance, is because you can have different providers that use the same TMessage in which case, I dont want every notifier's map that uses TMessage type to get invoked but only the one that the user requested at the start with AddProvider(provider1, map1) , I think you also have that problem, which to me seems like it wouldn't require a lot less work. If you have better idea of how this will work, can you please provide an example usage? :)
– Denis
Apr 4 at 14:51




I think it will be almost the same thing, the reason the current lookup looks complicated at first glance, is because you can have different providers that use the same TMessage in which case, I dont want every notifier's map that uses TMessage type to get invoked but only the one that the user requested at the start with AddProvider(provider1, map1) , I think you also have that problem, which to me seems like it wouldn't require a lot less work. If you have better idea of how this will work, can you please provide an example usage? :)
– Denis
Apr 4 at 14:51












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191202%2fcustom-observable-types-and-their-supporting-classes%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Chat program with C++ and SFML

Function to Return a JSON Like Objects Using VBA Collections and Arrays

Will my employers contract hold up in court?