11using System ;
22using System . Collections . Generic ;
3- using System . ComponentModel ;
43using System . Threading ;
54using KitX . Core . Contract . Event ;
6- using KitX . Shared . CSharp . Device ;
75using Serilog ;
86
97namespace KitX . Core . Event ;
@@ -27,6 +25,11 @@ public class EventService : IEventService
2725 /// </summary>
2826 private readonly object _lock = new ( ) ;
2927
28+ /// <summary>
29+ /// Maps (eventName, originalHandler) to wrapper lambda for typed subscribe/unsubscribe
30+ /// </summary>
31+ private readonly Dictionary < ( string eventName , Delegate handler ) , EventHandler < EventArgs > > _typedWrapperMap = new ( ) ;
32+
3033 /// <summary>
3134 /// Thread-local counter to track publish depth for recursion detection
3235 /// </summary>
@@ -95,12 +98,18 @@ public void Publish(string eventName, EventArgs args)
9598
9699 try
97100 {
98- if ( _eventHandlers . TryGetValue ( eventName , out var handlers ) )
101+ // Take a snapshot of handlers under lock to avoid concurrent modification
102+ List < EventHandler < EventArgs > > snapshot ;
103+ lock ( _lock )
99104 {
100- foreach ( var handler in handlers )
101- {
102- handler . Invoke ( this , args ) ;
103- }
105+ if ( ! _eventHandlers . TryGetValue ( eventName , out var handlers ) )
106+ return ;
107+ snapshot = new List < EventHandler < EventArgs > > ( handlers ) ;
108+ }
109+
110+ foreach ( var handler in snapshot )
111+ {
112+ handler . Invoke ( this , args ) ;
104113 }
105114 }
106115 finally
@@ -125,13 +134,16 @@ public void Subscribe<TEventArgs>(string eventName, EventHandler<TEventArgs> han
125134 _eventHandlers [ eventName ] = new List < EventHandler < EventArgs > > ( ) ;
126135 }
127136
128- _eventHandlers [ eventName ] . Add ( ( sender , args ) =>
137+ EventHandler < EventArgs > wrapper = ( sender , args ) =>
129138 {
130139 if ( args is TEventArgs typedArgs )
131140 {
132141 handler ( sender , typedArgs ) ;
133142 }
134- } ) ;
143+ } ;
144+
145+ _typedWrapperMap [ ( eventName , handler ) ] = wrapper ;
146+ _eventHandlers [ eventName ] . Add ( wrapper ) ;
135147 }
136148 }
137149
@@ -144,11 +156,17 @@ public void Subscribe<TEventArgs>(string eventName, EventHandler<TEventArgs> han
144156 public void Unsubscribe < TEventArgs > ( string eventName , EventHandler < TEventArgs > handler )
145157 where TEventArgs : EventArgs
146158 {
147- if ( _eventHandlers . TryGetValue ( eventName , out var handlers ) )
159+ lock ( _lock )
148160 {
149- // Note: Exact handler removal is not supported in this implementation
150- // The handler wrapper makes exact matching difficult
151- // Consider using a different approach for production
161+ var key = ( eventName , handler ) ;
162+ if ( _typedWrapperMap . TryGetValue ( key , out var wrapper ) )
163+ {
164+ if ( _eventHandlers . TryGetValue ( eventName , out var handlers ) )
165+ {
166+ handlers . Remove ( wrapper ) ;
167+ }
168+ _typedWrapperMap . Remove ( key ) ;
169+ }
152170 }
153171 }
154172
@@ -165,135 +183,4 @@ public void Publish<TEventArgs>(string eventName, TEventArgs args)
165183 Publish ( eventName , ( EventArgs ) args ) ;
166184 }
167185
168- /// <summary>
169- /// Invokes a static event dynamically (legacy support)
170- /// </summary>
171- /// <param name="eventName">The event name</param>
172- /// <param name="objects">Optional parameters</param>
173- [ Obsolete ( "Use IEventService.Publish with event names instead" ) ]
174- public static void Invoke ( string eventName , object [ ] ? objects = null )
175- {
176- var type = typeof ( EventService ) ;
177-
178- var eventField = type . GetField ( eventName , System . Reflection . BindingFlags . Static | System . Reflection . BindingFlags . NonPublic ) ;
179-
180- if ( eventField is null || ! typeof ( Delegate ) . IsAssignableFrom ( eventField . FieldType ) )
181- {
182- throw new ArgumentException ( $ "No event found with the name '{ eventName } '.", nameof ( eventName ) ) ;
183- }
184-
185- var @delegate = eventField . GetValue ( null ) as Delegate ;
186-
187- @delegate ? . DynamicInvoke ( objects ) ;
188- }
189-
190- #region Static Events (Legacy Support)
191-
192- #pragma warning disable CS0067 // Event is never used
193-
194- /// <summary>
195- /// Language changed event
196- /// </summary>
197- [ Obsolete ( "Use IEventService with EventNames.LanguageChanged instead" ) ]
198- public static event Action LanguageChanged = ( ) => { } ;
199-
200- /// <summary>
201- /// Greeting text interval updated event
202- /// </summary>
203- [ Obsolete ( "Use IEventService with EventNames.GreetingTextIntervalUpdated instead" ) ]
204- public static event Action GreetingTextIntervalUpdated = ( ) => { } ;
205-
206- /// <summary>
207- /// App config changed event
208- /// </summary>
209- [ Obsolete ( "Use IEventService with EventNames.AppConfigChanged instead" ) ]
210- public static event Action AppConfigChanged = ( ) => { } ;
211-
212- /// <summary>
213- /// Plugins config changed event
214- /// </summary>
215- [ Obsolete ( "Use IEventService with EventNames.PluginsConfigChanged instead" ) ]
216- public static event Action PluginsConfigChanged = ( ) => { } ;
217-
218- /// <summary>
219- /// Mica opacity changed event
220- /// </summary>
221- [ Obsolete ( "Use IEventService with EventNames.MicaOpacityChanged instead" ) ]
222- public static event Action MicaOpacityChanged = ( ) => { } ;
223-
224- /// <summary>
225- /// Develop settings changed event
226- /// </summary>
227- [ Obsolete ( "Use IEventService with EventNames.DevelopSettingsChanged instead" ) ]
228- public static event Action DevelopSettingsChanged = ( ) => { } ;
229-
230- /// <summary>
231- /// Log config updated event
232- /// </summary>
233- [ Obsolete ( "Use IEventService with EventNames.LogConfigUpdated instead" ) ]
234- public static event Action LogConfigUpdated = ( ) => { } ;
235-
236- /// <summary>
237- /// Theme config changed event
238- /// </summary>
239- [ Obsolete ( "Use IEventService with EventNames.ThemeConfigChanged instead" ) ]
240- public static event Action ThemeConfigChanged = ( ) => { } ;
241-
242- /// <summary>
243- /// Use statistics changed event
244- /// </summary>
245- [ Obsolete ( "Use IEventService with EventNames.UseStatisticsChanged instead" ) ]
246- public static event Action UseStatisticsChanged = ( ) => { } ;
247-
248- /// <summary>
249- /// Devices server port changed event
250- /// </summary>
251- [ Obsolete ( "Use IEventService with EventNames.DevicesServerPortChanged instead" ) ]
252- public static event Action < int > DevicesServerPortChanged = port => { } ;
253-
254- /// <summary>
255- /// Plugins server port changed event
256- /// </summary>
257- [ Obsolete ( "Use IEventService with EventNames.PluginsServerPortChanged instead" ) ]
258- public static event Action < int > PluginsServerPortChanged = port => { } ;
259-
260- /// <summary>
261- /// Activities updated event
262- /// </summary>
263- [ Obsolete ( "Use IEventService with EventNames.OnActivitiesUpdated instead" ) ]
264- public static event Action OnActivitiesUpdated = ( ) => { } ;
265-
266- /// <summary>
267- /// Receive cancel exchanging device key event
268- /// </summary>
269- [ Obsolete ( "Use IEventService with EventNames.OnReceiveCancelExchangingDeviceKey instead" ) ]
270- public static event Action OnReceiveCancelExchangingDeviceKey = ( ) => { } ;
271-
272- /// <summary>
273- /// Exiting event
274- /// </summary>
275- [ Obsolete ( "Use IEventService with EventNames.OnExiting instead" ) ]
276- public static event Action OnExiting = ( ) => { } ;
277-
278- /// <summary>
279- /// Receiving device info event
280- /// </summary>
281- [ Obsolete ( "Use IEventService with EventNames.OnReceivingDeviceInfo instead" ) ]
282- public static event Action < DeviceInfo > OnReceivingDeviceInfo = _ => { } ;
283-
284- /// <summary>
285- /// Config hot reloaded event
286- /// </summary>
287- [ Obsolete ( "Use IEventService with EventNames.OnConfigHotReloaded instead" ) ]
288- public static event Action OnConfigHotReloaded = ( ) => { } ;
289-
290- /// <summary>
291- /// Accepting device key event
292- /// </summary>
293- [ Obsolete ( "Use IEventService with EventNames.OnAcceptingDeviceKey instead" ) ]
294- public static event Action < string > OnAcceptingDeviceKey = _ => { } ;
295-
296- #pragma warning restore CS0067 // Event is never used
297-
298- #endregion
299186}
0 commit comments