Files
DP44/enriched-qwen3-coder-next/Common/DTS.Common.Core/EventManager.md
2026-04-17 14:55:32 -04:00

7.1 KiB

source_files, generated_at, model, schema_version, sha256
source_files generated_at model schema_version sha256
Common/DTS.Common.Core/EventManager/EventManager.cs
2026-04-16T02:06:02.522135+00:00 Qwen/Qwen3-Coder-Next-FP8 1 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 UnSubscribed, 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.