//некое событие
public interface IEvent{}
 
//обработчик события
public interface IEventHandler<T> where T : class, IEvent
{
    public void HandleEvent(T @event);
}
 
public class EventDispatcher{
    private Dictionary<Type, List<WeakReference>> _handlers = new();
    private object _locker = new();
    public void Subscribe<T>(IEventHandler<T> handler) where T : class, IEvent
    {
        var type = typeof(T);
        lock(_locker){
            if(!_handlers.TryGetValue(type, out var handlers)){
                handlers = new();
                _handlers[type] = handlers;
            }
            var refs = handlers.Find(x=>x.Target == handler);
            if(refs is not null){
                throw new InvalidOperationException();
            }
            refs = new(handler);
            handlers.Add(refs);            
        }
    }
 
    public void Unsubscribe<T>(IEventHandler<T> handler) where T : class, IEvent
    {
        var type = typeof(T);
        lock(_locker){
            if(!_handlers.ContainsKey(type)){
                throw new InvalidOperationException();
            }
            var handlers = _handlers[type];
            var refs = handlers.Find(x=>x.Target == handlers);
            if(refs is null){
                throw new InvalidOperationException();
            }
            handlers.Remove(refs);
        }
    }
 
    public void Handle<T>(T @event) where T : class, IEvent
    {
        var type = typeof(T);
        //не даем подписываться пока идет обработка)))
        lock(_locker){
            if(!_handlers.ContainsKey(type)){
                return;
            }
            var handlers = _handlers[type];
            List<WeakReference> toRemove = new();
            foreach(var handlerRef in handlers){
                if(!handlerRef.IsAlive){
                    toRemove.Add(handlerRef);
                    continue;
                }
                if(handlerRef.Target is null){
                    toRemove.Add(handlerRef);
                    continue;
                }
                var handler = handlerRef.Target as IEventHandler<T>;
                if(handler is null){
                    toRemove.Add(handlerRef);
                    continue;
                }
                handler.HandleEvent(@event);
            }
            foreach(var i in toRemove){
                handlers.Remove(i);
            }
        }
    }
}