@@ -12,8 +12,8 @@ namespace LoreSoft.Blazor.Controls.Events;
1212/// </para>
1313/// <para>
1414/// Events are dispatched based on their type, and all handlers for a given event type are invoked
15- /// concurrently when the event is published. Empty subscriptions (with no remaining handlers) are
16- /// automatically removed from the event bus .
15+ /// concurrently when the event is published. Event-type buckets may remain cached after all handlers
16+ /// are removed; individual handler entries are weakly held and pruned by the underlying event store .
1717/// </para>
1818/// <para>
1919/// All operations are thread-safe and use concurrent collections internally.
@@ -39,8 +39,8 @@ public void Subscribe<TEvent>(Func<TEvent, ValueTask> handler)
3939
4040 var messageType = typeof ( TEvent ) ;
4141
42- var subscription = _subscriptions . GetOrAdd ( messageType , _ => new WeakEvent < TEvent > ( ) ) as WeakEvent < TEvent > ;
43- subscription ? . Subscribe ( handler ) ;
42+ if ( _subscriptions . GetOrAdd ( messageType , _ => new WeakEvent < TEvent > ( ) ) is WeakEvent < TEvent > subscription )
43+ subscription . Subscribe ( handler ) ;
4444 }
4545
4646 /// <summary>
@@ -60,8 +60,8 @@ public void Subscribe<TEvent>(Func<TEvent, CancellationToken, ValueTask> handler
6060
6161 var messageType = typeof ( TEvent ) ;
6262
63- var subscription = _subscriptions . GetOrAdd ( messageType , _ => new WeakEvent < TEvent > ( ) ) as WeakEvent < TEvent > ;
64- subscription ? . Subscribe ( handler ) ;
63+ if ( _subscriptions . GetOrAdd ( messageType , _ => new WeakEvent < TEvent > ( ) ) is WeakEvent < TEvent > subscription )
64+ subscription . Subscribe ( handler ) ;
6565 }
6666
6767 /// <summary>
@@ -71,21 +71,18 @@ public void Subscribe<TEvent>(Func<TEvent, CancellationToken, ValueTask> handler
7171 /// <param name="handler">The asynchronous function to unsubscribe. Cannot be <see langword="null"/>.</param>
7272 /// <exception cref="ArgumentNullException">Thrown when <paramref name="handler"/> is <see langword="null"/>.</exception>
7373 /// <remarks>
74- /// If this was the last handler for the event type, the subscription is removed from the event bus .
74+ /// Removing the last handler clears the underlying event store, but the event-type bucket may remain cached .
7575 /// </remarks>
7676 public void Unsubscribe < TEvent > ( Func < TEvent , ValueTask > handler )
7777 {
7878 ArgumentNullException . ThrowIfNull ( handler ) ;
7979
8080 var messageType = typeof ( TEvent ) ;
81- if ( _subscriptions . GetOrAdd ( messageType , _ => new WeakEvent < TEvent > ( ) ) is not WeakEvent < TEvent > subscription )
82- return ;
8381
84- var remaining = subscription . Unsubscribe ( handler ) ;
85- if ( remaining > 0 )
82+ if ( ! _subscriptions . TryGetValue ( messageType , out var weakEvent ) || weakEvent is not WeakEvent < TEvent > subscription )
8683 return ;
8784
88- _subscriptions . TryRemove ( messageType , out _ ) ;
85+ subscription . Unsubscribe ( handler ) ;
8986 }
9087
9188 /// <summary>
@@ -95,21 +92,18 @@ public void Unsubscribe<TEvent>(Func<TEvent, ValueTask> handler)
9592 /// <param name="handler">The asynchronous function that accepts a <see cref="CancellationToken"/> to unsubscribe. Cannot be <see langword="null"/>.</param>
9693 /// <exception cref="ArgumentNullException">Thrown when <paramref name="handler"/> is <see langword="null"/>.</exception>
9794 /// <remarks>
98- /// If this was the last handler for the event type, the subscription is removed from the event bus .
95+ /// Removing the last handler clears the underlying event store, but the event-type bucket may remain cached .
9996 /// </remarks>
10097 public void Unsubscribe < TEvent > ( Func < TEvent , CancellationToken , ValueTask > handler )
10198 {
10299 ArgumentNullException . ThrowIfNull ( handler ) ;
103100
104101 var messageType = typeof ( TEvent ) ;
105- if ( _subscriptions . GetOrAdd ( messageType , _ => new WeakEvent < TEvent > ( ) ) is not WeakEvent < TEvent > subscription )
106- return ;
107102
108- var remaining = subscription . Unsubscribe ( handler ) ;
109- if ( remaining > 0 )
103+ if ( ! _subscriptions . TryGetValue ( messageType , out var weakEvent ) || weakEvent is not WeakEvent < TEvent > subscription )
110104 return ;
111105
112- _subscriptions . TryRemove ( messageType , out _ ) ;
106+ subscription . Unsubscribe ( handler ) ;
113107 }
114108
115109 /// <summary>
@@ -119,31 +113,18 @@ public void Unsubscribe<TEvent>(Func<TEvent, CancellationToken, ValueTask> handl
119113 /// <exception cref="ArgumentNullException">Thrown when <paramref name="subscriber"/> is <see langword="null"/>.</exception>
120114 /// <remarks>
121115 /// This method removes all handlers across all event types that belong to the specified subscriber.
122- /// Empty subscriptions (with no remaining handlers) are automatically removed from the event bus .
116+ /// Event-type buckets may remain cached after all matching handlers are removed .
123117 /// </remarks>
124118 public void Unsubscribe ( object subscriber )
125119 {
126120 ArgumentNullException . ThrowIfNull ( subscriber ) ;
127121
128- List < Type > ? emptySubscriptions = null ;
129122 foreach ( var pair in _subscriptions )
130123 {
131- var subscriberType = pair . Key ;
132124 var subscription = pair . Value ;
133125
134- var remaining = subscription . Unsubscribe ( subscriber ) ;
135- if ( remaining > 0 )
136- continue ;
137-
138- emptySubscriptions ??= [ ] ;
139- emptySubscriptions . Add ( subscriberType ) ;
126+ subscription . Unsubscribe ( subscriber ) ;
140127 }
141-
142- if ( emptySubscriptions == null || emptySubscriptions . Count == 0 )
143- return ;
144-
145- foreach ( var type in emptySubscriptions )
146- _subscriptions . TryRemove ( type , out _ ) ;
147128 }
148129
149130 /// <summary>
@@ -162,7 +143,7 @@ public void Unsubscribe(object subscriber)
162143 /// <exception cref="ArgumentNullException">Thrown when <paramref name="eventData"/> is <see langword="null"/>.</exception>
163144 /// <remarks>
164145 /// <para>
165- /// Dead handlers (whose targets have been garbage collected) are automatically removed before invocation.
146+ /// Dead handlers (whose targets have been garbage collected) are skipped during invocation and may be pruned asynchronously .
166147 /// </para>
167148 /// <para>
168149 /// All handlers are invoked concurrently, and this method awaits all of them to complete.
0 commit comments