I'm developing a mediator for an MVVM application. This application lets employees file tickets with IT support to ask for help.The mediator communicates messages and triggers callbacks between ViewModels. Here's a description of it's architecture : The messages between the ViewModelsThe messages are stored in an enumerator :public enum ViewModelMessages{    UserCreated,    ManagerChanged,    TicketCreated,    TicketDeleted,    TicketRefusedByIT,    CommentAppendedToTicket,    TicketCompleted,    StatisticsFileOutputted};This list of messages could get a lot longer than that as I continue development.The MediatorThe mediator is a singleton that can register multiple callbacks for each message and triggers them when the message is emitted. These callbacks can take no parameters or a single object parameter of any given type : public sealed class Mediator {    #region Singleton definition    private static readonly Lazy<Mediator> lazy = new Lazy<Mediator>(() => new Mediator());    public static Mediator Instance {        get {            return lazy.Value;        }    }    private Mediator() {    }    #endregion    private MultiDictionary<ViewModelMessages, Action> registeredCallbacksWithoutParameter = new MultiDictionary<ViewModelMessages, Action>();    private MultiDictionary<ViewModelMessages, Action<object>> registeredCallbacksWithParameter = new MultiDictionary<ViewModelMessages, Action<object>>();    public void Register(ViewModelMessages message, Action callback) {        registeredCallbacksWithoutParameter[message] += callback;    }    public void Register<ParameterType>(ViewModelMessages message, Action<ParameterType> callback) {        registeredCallbacksWithParameter[message] +=            (parameter) => callback((ParameterType)parameter);    }    public void Unregister(ViewModelMessages message, Action callback) {        registeredCallbacksWithoutParameter[message] -= callback;    }    public void Unregister(ViewModelMessages message, Action<object> callback) {        registeredCallbacksWithParameter[message] -= callback;    }    public void Notify(ViewModelMessages message) {        foreach(Action callback in registeredCallbacksWithoutParameter[message]) {            callback();        }    }    public void Notify(ViewModelMessages message, object parameter) {        foreach(Action callback in registeredCallbacksWithoutParameter[message]) {            callback();        }        foreach(Action<object> callback in registeredCallbacksWithParameter[message]) {            callback(parameter);        }    }}The MultiDictionaryThe callbacks are stored in a MultiDictionary: a regular dictionary where each entry can store a hashset : public class MultiDictionary<keyType, contentType> : IDictionary<keyType, AddableHashSet<contentType>> {    private Dictionary<keyType, AddableHashSet<contentType>> dictionary = new Dictionary<keyType, AddableHashSet<contentType>>();    public AddableHashSet<contentType> this[keyType key] {        get {            EnsureKey(key);            return dictionary[key];        }        set {            EnsureKey(key);            dictionary[key] = value;            if(dictionary[key].Count == 0) {                dictionary.Remove(key);            }        }    }    private void EnsureKey(keyType key) {        if(!ContainsKey(key)) {            dictionary.Add(key, new AddableHashSet<contentType>());        }        else {            if(dictionary[key] == null)                dictionary[key] = new AddableHashSet<contentType>();        }    }    #region IDictionary interface implementation via the dictionnary member variable    public ICollection<keyType> Keys => dictionary.Keys;    public ICollection<AddableHashSet<contentType>> Values => dictionary.Values;    public int Count => dictionary.Count;    public bool IsReadOnly => ((IDictionary<keyType, AddableHashSet<contentType>>)dictionary).IsReadOnly;    public void Add(keyType key, AddableHashSet<contentType> value) => dictionary.Add(key, value);    public void Add(KeyValuePair<keyType, AddableHashSet<contentType>> item) => throw new NotImplementedException();    public void Clear() => dictionary.Clear();    public bool Contains(KeyValuePair<keyType, AddableHashSet<contentType>> item) => throw new NotImplementedException();    public bool ContainsKey(keyType key) => dictionary.ContainsKey(key);    public void CopyTo(KeyValuePair<keyType, AddableHashSet<contentType>>[] array, int arrayIndex) => throw new NotImplementedException();    public IEnumerator<KeyValuePair<keyType, AddableHashSet<contentType>>> GetEnumerator() => dictionary.GetEnumerator();    public bool Remove(keyType key) => dictionary.Remove(key);    public bool Remove(KeyValuePair<keyType, AddableHashSet<contentType>> item) => throw new NotImplementedException();    public bool TryGetValue(keyType key, out AddableHashSet<contentType> value) => dictionary.TryGetValue(key, out value);    IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();    #endregion}The number of callbacks per message will potentially be between 0 and 10, but it could potentially go a lot higher than that. At this stage I still don't know how high so I preferred to use a hashet instead of a list to avoid it being a bottleneck later on.The hashsetThe AddableHashSet is a hashset with the + and - operators overloaded to simplify the syntax in the Mediator: using += and -= follows the event += eventhandler() pattern.public class AddableHashSet<Type> : HashSet<Type> {    public static AddableHashSet<Type> operator +(AddableHashSet<Type> hashSet, Type value) {        hashSet.Add(value);        return hashSet;    }    public static AddableHashSet<Type> operator -(AddableHashSet<Type> list, Type value) {        if(list.Contains(value)) {            list.Remove(value);        }        return list;    }    public AddableHashSet() {    }}What do you think?This is my first implementation of the mediator pattern ever. Any suggestions would be greatly appreciated!
ViewModels Mediator with multiple generic callbacks
c#;callback;mvvm;mediator
I don't feel like you should override + and -. Fine, it makes the syntax cuter, but it makes your code much less readable for someone who isn't you.Remember, you write code so that yourself in 5 years or someone else can read it properly, not so you can feel it better right now. Follow this guideline, and you'll see that your code will be much cleaner.By using the + operator, we loose the advantage of the bool Add(T value) that the Hashset<T> has.Following coding conventions, your generic types should be PascalCased. So keyType = KeyType and contentType = ContentType.You could make your dictionary variable readonly. The main purpose is to tell other programmers (or yourself in 5 years) that this variable should never be set somewhere else than at it's declaration.I'm wondering, why is there a s at the end of ViewModelMessages? Is it a list of view models? Your Mediator looks fine, though I feel like there might be a problem when it comes to Unregister. Imagine this :Mediator.Instance.Register(ViewModelMessages.UserCreated,() => someAction);//Later in a far far away block of codeMediator.Instance.Unregister(ViewModelMessages.UserCreated, () => someAction);Will it work? After all, the lambda isn't the same reference as before! Your dictionary might not find it. (Although I must say I'm not 100% sure)This is why, when you look at the IObservable/IObserver pattern, IObservable<T>.Subscribe returns IDisposable, which is the object responsible to Unsubscribe the Observer, you should implement something similar in my opinion because you might not always have the opportunity to Unregister your callback when you want (example if you need to do it in a DLL that has no reference to your Mediator.Last thing, I don't think it should be a Singleton. I think your Mediator should be backed by an interface (IMediator perhaps) so you can unit test your code more easily and create less dependance around your code. (If you need to understand why interfaces are nice, I'll post some articles in comment if you want) And you could use an IoC container to deal with the dependances.