Custom observable types and their supporting classes
Clash 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.
c# performance .net generics event-handling
add a comment |Â
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.
c# performance .net generics event-handling
1
Feeling like reinventing the observer pattern? ;-)
â t3chb0t
Apr 4 at 6:21
add a comment |Â
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.
c# performance .net generics event-handling
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.
c# performance .net generics event-handling
asked Apr 3 at 21:48
Denis
6,16021453
6,16021453
1
Feeling like reinventing the observer pattern? ;-)
â t3chb0t
Apr 4 at 6:21
add a comment |Â
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
add a comment |Â
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 Action
s.
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).
This looks very familiar... almost like theSubject
from the reactive extensions that combines both theIObserver
and theIObservable
interfaces.
â t3chb0t
Apr 4 at 8:23
The point is that whenever Notifier X fires his Notify event, if X has already been mapped inMultipleProvidersCache
along with anObservableMap
, 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 hadEventArgs
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 sameTMessage
in which case, I dont want every notifier's map that usesTMessage
type to get invoked but only the one that the user requested at the start withAddProvider(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
add a comment |Â
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 Action
s.
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).
This looks very familiar... almost like theSubject
from the reactive extensions that combines both theIObserver
and theIObservable
interfaces.
â t3chb0t
Apr 4 at 8:23
The point is that whenever Notifier X fires his Notify event, if X has already been mapped inMultipleProvidersCache
along with anObservableMap
, 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 hadEventArgs
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 sameTMessage
in which case, I dont want every notifier's map that usesTMessage
type to get invoked but only the one that the user requested at the start withAddProvider(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
add a comment |Â
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 Action
s.
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).
This looks very familiar... almost like theSubject
from the reactive extensions that combines both theIObserver
and theIObservable
interfaces.
â t3chb0t
Apr 4 at 8:23
The point is that whenever Notifier X fires his Notify event, if X has already been mapped inMultipleProvidersCache
along with anObservableMap
, 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 hadEventArgs
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 sameTMessage
in which case, I dont want every notifier's map that usesTMessage
type to get invoked but only the one that the user requested at the start withAddProvider(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
add a comment |Â
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 Action
s.
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).
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 Action
s.
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).
edited Apr 4 at 14:19
answered Apr 4 at 8:12
Nikita B
12.3k11652
12.3k11652
This looks very familiar... almost like theSubject
from the reactive extensions that combines both theIObserver
and theIObservable
interfaces.
â t3chb0t
Apr 4 at 8:23
The point is that whenever Notifier X fires his Notify event, if X has already been mapped inMultipleProvidersCache
along with anObservableMap
, 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 hadEventArgs
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 sameTMessage
in which case, I dont want every notifier's map that usesTMessage
type to get invoked but only the one that the user requested at the start withAddProvider(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
add a comment |Â
This looks very familiar... almost like theSubject
from the reactive extensions that combines both theIObserver
and theIObservable
interfaces.
â t3chb0t
Apr 4 at 8:23
The point is that whenever Notifier X fires his Notify event, if X has already been mapped inMultipleProvidersCache
along with anObservableMap
, 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 hadEventArgs
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 sameTMessage
in which case, I dont want every notifier's map that usesTMessage
type to get invoked but only the one that the user requested at the start withAddProvider(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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
Feeling like reinventing the observer pattern? ;-)
â t3chb0t
Apr 4 at 6:21