97 lines
7.1 KiB
Markdown
97 lines
7.1 KiB
Markdown
|
|
---
|
||
|
|
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<in T>(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>(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<T>(SubscriberCallbackDelegate<T> listener) where T : class`**
|
||
|
|
Registers `listener` for all events of type `T` (no filtering). Equivalent to `Subscribe(listener, null)`.
|
||
|
|
|
||
|
|
- **`static void Subscribe<T>(SubscriberCallbackDelegate<T> listener, Predicate<T> 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<T>` instance.
|
||
|
|
|
||
|
|
- **`static void UnSubscribe<T>(SubscriberCallbackDelegate<T> 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<T>` 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<T>` 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<T>` is internal**: While `EventManager` is public, the `EventMetaData<T>` 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`.
|