--- source_files: - Common/DTS.Common.Core/EventManager/EventManager.cs generated_at: "2026-04-16T02:06:02.522135+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "59cbfaadaa1e572f" --- # EventManager ## Documentation: `EventManager` Module ### 1. Purpose The `EventManager` module provides a lightweight, type-safe event pub/sub mechanism that decouples event publishers from subscribers. It enables components to publish events of arbitrary types without compile-time knowledge of listeners, and allows subscribers to register callbacks—optionally with predicate-based filters—to receive only relevant events. Diagnostic instrumentation is built-in to track subscription lifecycle events (add, remove, publish) for observability. This module serves as a core infrastructure component for in-process event-driven communication within the DTS system. --- ### 2. Public Interface #### `delegate void SubscriberCallbackDelegate(T item) where T : class` A generic delegate for event listeners. Receives the event data (`item`) and returns `void`. Contravariant in `T`. #### `delegate void DiagnosticCallbackDelegate(EventDiagnosticType eventType, Type t, object eventData, string listener)` A delegate for diagnostic event handlers. Invoked on all diagnostic events with: - `eventType`: the diagnostic action (`EventDiagnosticType`) - `t`: the event type being subscribed/published/unsubscribed (or `null` for global operations) - `eventData`: the event payload (or `null` for subscription lifecycle events) - `listener`: fully qualified method name and assembly info (e.g., `"MyNamespace.MyClass.MyMethod, MyAssembly, Version=..."`) #### `static class EventManager` A static class managing event subscriptions and publishing. - **`static void Publish(T eventData) where T : class`** Publishes `eventData` to all subscribers registered for type `T`. Filters (if any) are applied before invoking callbacks. Diagnostic events are emitted for each callback invocation. - **`static void Subscribe(SubscriberCallbackDelegate listener) where T : class`** Registers `listener` for all events of type `T` (no filtering). Equivalent to `Subscribe(listener, null)`. - **`static void Subscribe(SubscriberCallbackDelegate listener, Predicate eventFilter) where T : class`** Registers `listener` for events of type `T`, but only invokes it when `eventFilter(eventData)` returns `true`. Internally wraps listener and filter in an `EventMetaData` instance. - **`static void UnSubscribe(SubscriberCallbackDelegate listener) where T : class`** Removes *all* registrations of `listener` for type `T`. Uses reference equality on the delegate. - **`static void Clear()`** Removes *all* event subscriptions (all types, all listeners). Emits a diagnostic event with `EventDiagnosticType.RemoveListener`. - **`static void SubscribeToDiagnosticEvents(DiagnosticCallbackDelegate listener)`** Registers a diagnostic listener. Invoked on *every* diagnostic event (including those triggered by diagnostic subscription itself). - **`static void UnSubscribeToDiagnosticEvents(DiagnosticCallbackDelegate listener)`** Removes a specific diagnostic listener. - **`static void ClearDiagnosticEvents()`** Removes *all* diagnostic listeners. --- ### 3. Invariants - **Type-based isolation**: Subscriptions and publications are strictly keyed by the *exact* runtime type `T`. Subscribing to `BaseEvent` does not receive events of type `DerivedEvent : BaseEvent`. - **Filter semantics**: Filters are applied *per subscription* during `Publish`. If `EventFilter` is `null`, the callback is always invoked. - **Reference equality for unsubscription**: `UnSubscribe` uses `==` (reference equality) on the delegate instance. Two different delegate instances—even if pointing to the same method—will not be considered equal. - **Diagnostic event ordering**: Diagnostic events are emitted *synchronously* and in the order of subscription: - `AddListener` when `Subscribe` is called - `PublishEvent` for each callback invocation during `Publish` - `RemoveListener` when `UnSubscribe` is called - `RemoveListener` (global) when `Clear` is called - Diagnostic-specific events (`AddListenerDiagnostic`, `RemoveListenerDiagnostic`) follow the same pattern for diagnostic listeners. - **No null safety for `eventData`**: `Publish` does *not* validate `eventData` is non-null. If `eventData` is `null`, it is passed as-is to callbacks and filters. - **No thread-safety**: The module makes no guarantees about concurrent access. `SubscriberList` and `DiagnosticList` are not thread-safe collections. --- ### 4. Dependencies #### Dependencies *of* `EventManager` - **`System`**: Core runtime (`System`, `System.Collections.Generic`, `System.Reflection`) - **No external libraries**: Pure .NET Framework/BCL usage. #### Dependencies *on* `EventManager` - **Inferred consumers**: Any component in the codebase that uses `EventManager.Subscribe`, `EventManager.Publish`, or diagnostic hooks. - **No direct reverse dependencies** are visible in this file, but the presence of `DiagnosticCallbackDelegate` and `EventDiagnosticType` suggests integration with logging, telemetry, or debugging infrastructure (e.g., a diagnostic monitor or test harness). --- ### 5. Gotchas - **No deduplication on subscription**: Calling `Subscribe` multiple times with the *same* listener and filter results in multiple registrations. `UnSubscribe` must be called the same number of times to fully remove it. - **Filter method reference in diagnostics**: The `listener` string passed to `DiagnosticCallbackDelegate` includes the *filter method* (if present) during `PublishEvent` diagnostics—not the callback method. This is because `metaData.EventFilter?.Method` is used. - **Diagnostic listener recursion risk**: A diagnostic listener that itself calls `EventManager.Publish` or `EventManager.Subscribe` will trigger *nested* diagnostic events (e.g., publishing an event while processing a diagnostic event). This is not prevented. - **`Clear()` is global and destructive**: `Clear()` removes *all* subscriptions across *all* event types. Use with caution in long-lived systems (e.g., during shutdown). - **`EventMetaData` is internal**: While `EventManager` is public, the `EventMetaData` class is internal. This means external code cannot introspect or manipulate subscription metadata directly. - **No weak references**: Subscribers are held via strong references. If a subscriber object is not explicitly `UnSubscribe`d, it may be kept alive indefinitely (memory leak risk). - **`DiagnosticCallbackDelegate` signature mismatch in `ClearDiagnosticEvents`**: When `UnSubscribeToDiagnosticEvents` is called, it passes `listener.Method` to `SendDiagnosticEvent`, but `listener` may be `null` if the listener was already removed. However, `UnSubscribeToDiagnosticEvents` only removes existing entries, so `listener` is non-null *at the time of call*. Still, `SendDiagnosticEvent` handles `listenerMethod == null` gracefully. - **No async support**: All callbacks are invoked synchronously. Long-running callbacks will block `Publish`.