feat(events): 添加增强事件总线支持过滤器和统计功能#83
Conversation
- 实现 EnhancedEventBus 支持过滤器、弱引用事件和统计功能 - 添加 FilterableEvent 和 WeakEvent 支持事件过滤和弱引用订阅 - 实现 PredicateEventFilter 和 SamplingEventFilter 事件过滤器 - 添加 EventStatistics 统计事件发布、处理和失败次数 - 实现完整的单元测试验证过滤器和统计功能
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| C# | Mar 6, 2026 8:58a.m. | Review ↗ | |
| Secrets | Mar 6, 2026 8:58a.m. | Review ↗ |
评审者指南引入一个 EnhancedEventBus,在现有的 EasyEvents/PriorityEvent 基础设施之上分层支持可过滤事件和弱事件,增加可插拔事件过滤器,并集中进行事件统计跟踪,同时为弱事件、过滤器和统计提供全面的单元测试。 发送带谓词过滤器与采样过滤器并带统计信息的可过滤事件的序列图sequenceDiagram
participant Client
participant EnhancedEventBus
participant FilterableEvent_T_
participant PredicateEventFilter_T_
participant SamplingEventFilter_T_
participant EventStatistics
participant Listener
Client->>EnhancedEventBus: RegisterFilterable_T_(onEvent)
EnhancedEventBus->>FilterableEvent_T_: GetOrAdd TestEvent
FilterableEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
FilterableEvent_T_-->>EnhancedEventBus: IUnRegister
EnhancedEventBus-->>Client: IUnRegister
Client->>EnhancedEventBus: AddFilter_T_(PredicateEventFilter_T_)
EnhancedEventBus->>FilterableEvent_T_: AddFilter(predicateFilter)
Client->>EnhancedEventBus: AddFilter_T_(SamplingEventFilter_T_)
EnhancedEventBus->>FilterableEvent_T_: AddFilter(samplingFilter)
Client->>EnhancedEventBus: SendFilterable_T_(data)
EnhancedEventBus->>FilterableEvent_T_: Trigger(data)
FilterableEvent_T_->>EventStatistics: RecordPublish(TestEvent)
%% Apply filters
FilterableEvent_T_->>PredicateEventFilter_T_: ShouldFilter(data)
alt PredicateFilter blocks
PredicateEventFilter_T_-->>FilterableEvent_T_: true
FilterableEvent_T_-->>EnhancedEventBus: return (event filtered)
else PredicateFilter passes
PredicateEventFilter_T_-->>FilterableEvent_T_: false
FilterableEvent_T_->>SamplingEventFilter_T_: ShouldFilter(data)
alt SamplingFilter blocks
SamplingEventFilter_T_-->>FilterableEvent_T_: true
FilterableEvent_T_-->>EnhancedEventBus: return (event sampled out)
else SamplingFilter passes
SamplingEventFilter_T_-->>FilterableEvent_T_: false
FilterableEvent_T_->>Listener: onEvent(data)
Listener-->>FilterableEvent_T_: handled
FilterableEvent_T_->>EventStatistics: RecordHandle()
end
end
发送带自动清理与统计信息的弱事件的序列图sequenceDiagram
participant Client
participant EnhancedEventBus
participant WeakEvent_T_
participant EventStatistics
participant AliveListener
participant DeadListener
Client->>EnhancedEventBus: RegisterWeak_T_(DeadListener.OnEvent)
EnhancedEventBus->>WeakEvent_T_: Register(DeadListener)
WeakEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
Client->>EnhancedEventBus: RegisterWeak_T_(AliveListener.OnEvent)
EnhancedEventBus->>WeakEvent_T_: Register(AliveListener)
WeakEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
Note over DeadListener: DeadListener 变为不可达
Client->>Client: GC.Collect()
Client->>EnhancedEventBus: SendWeak_T_(data)
EnhancedEventBus->>WeakEvent_T_: Trigger(data)
WeakEvent_T_->>EventStatistics: RecordPublish(TestEvent)
WeakEvent_T_->>WeakEvent_T_: Collect alive handlers
WeakEvent_T_->>DeadListener: TryGetTarget()
DeadListener-->>WeakEvent_T_: collected (removed)
WeakEvent_T_->>AliveListener: TryGetTarget()
AliveListener-->>WeakEvent_T_: alive
WeakEvent_T_->>AliveListener: OnEvent(data)
AliveListener-->>WeakEvent_T_: handled
WeakEvent_T_->>EventStatistics: RecordHandle()
WeakEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
带过滤器、弱事件和统计信息的 EnhancedEventBus 类图classDiagram
class EnhancedEventBus {
-EasyEvents _mEvents
-ConcurrentDictionary~Type, object~ _mFilterableEvents
-EasyEvents _mPriorityEvents
-ConcurrentDictionary~Type, object~ _mWeakEvents
-EventStatistics _statistics
+EnhancedEventBus(enableStatistics bool)
+IEventStatistics Statistics
+void Send_T_()
+void Send_T_(e T)
+void Send_T_(e T, propagation EventPropagation)
+IUnRegister Register_T_(onEvent Action_T_)
+IUnRegister Register_T_(onEvent Action_T_, priority int)
+void UnRegister_T_(onEvent Action_T_)
+IUnRegister RegisterWithContext_T_(onEvent Action_EventContext_T__)
+IUnRegister RegisterWithContext_T_(onEvent Action_EventContext_T__, priority int)
+void SendFilterable_T_(e T)
+IUnRegister RegisterFilterable_T_(onEvent Action_T_)
+void AddFilter_T_(filter IEventFilter_T_)
+void RemoveFilter_T_(filter IEventFilter_T_)
+void ClearFilters_T_()
+void SendWeak_T_(e T)
+IUnRegister RegisterWeak_T_(onEvent Action_T_)
+void CleanupWeak_T_()
}
class FilterableEvent_T_ {
-List~IEventFilter_T_~ _filters
-object _lock
-EventStatistics _statistics
-Action_T_ _onEvent
+FilterableEvent_T_(statistics EventStatistics)
+IUnRegister Register(onEvent Action_T_)
+void UnRegister(onEvent Action_T_)
+void Trigger(data T)
+void AddFilter(filter IEventFilter_T_)
+void RemoveFilter(filter IEventFilter_T_)
+void ClearFilters()
+int GetListenerCount()
}
class WeakEvent_T_ {
-object _lock
-EventStatistics _statistics
-List~WeakReference~Action_T_~~ _weakHandlers
+WeakEvent_T_(statistics EventStatistics)
+IUnRegister Register(onEvent Action_T_)
+void UnRegister(onEvent Action_T_)
+void Trigger(data T)
+void Cleanup()
+int GetListenerCount()
}
class EventStatistics {
-Dictionary~string, int~ _listenerCountByType
-Dictionary~string, long~ _publishCountByType
-object _lock
-int _activeEventTypes
-int _activeListeners
-long _totalFailed
-long _totalHandled
-long _totalPublished
+long TotalPublished
+long TotalHandled
+long TotalFailed
+int ActiveEventTypes
+int ActiveListeners
+long GetPublishCount(eventType string)
+int GetListenerCount(eventType string)
+void Reset()
+string GenerateReport()
+void RecordPublish(eventType string)
+void RecordHandle()
+void RecordFailure()
+void UpdateListenerCount(eventType string, count int)
}
class IEventStatistics {
<<interface>>
+long TotalPublished
+long TotalHandled
+long TotalFailed
+int ActiveEventTypes
+int ActiveListeners
+long GetPublishCount(eventType string)
+int GetListenerCount(eventType string)
+void Reset()
+string GenerateReport()
}
class IEventFilter_T_ {
<<interface>>
+bool ShouldFilter(eventData T)
}
class PredicateEventFilter_T_ {
-Func_T_, bool_ _predicate
+PredicateEventFilter_T_(predicate Func_T_, bool_)
+bool ShouldFilter(eventData T)
}
class SamplingEventFilter_T_ {
-double _samplingRate
-long _counter
+SamplingEventFilter_T_(samplingRate double)
+bool ShouldFilter(eventData T)
}
class IUnRegister {
<<interface>>
+void UnRegister()
}
class DefaultUnRegister {
-Action _onUnRegister
+DefaultUnRegister(onUnRegister Action)
+void UnRegister()
}
class IEventBus {
<<interface>>
}
class EasyEvents
class Event_T_
class PriorityEvent_T_
class EventContext_T_
EnhancedEventBus ..|> IEventBus
EventStatistics ..|> IEventStatistics
EnhancedEventBus --> EventStatistics : uses
EnhancedEventBus --> FilterableEvent_T_ : manages
EnhancedEventBus --> WeakEvent_T_ : manages
EnhancedEventBus --> EasyEvents : uses
EnhancedEventBus --> PriorityEvent_T_ : uses
FilterableEvent_T_ --> IEventFilter_T_ : aggregates
FilterableEvent_T_ --> EventStatistics : updates
WeakEvent_T_ --> EventStatistics : updates
PredicateEventFilter_T_ ..|> IEventFilter_T_
SamplingEventFilter_T_ ..|> IEventFilter_T_
FilterableEvent_T_ --> IUnRegister : returns
WeakEvent_T_ --> IUnRegister : returns
DefaultUnRegister ..|> IUnRegister
EasyEvents --> Event_T_ : manages
EasyEvents --> PriorityEvent_T_ : manages
PriorityEvent_T_ --> EventContext_T_
文件级变更
使用提示与命令与 Sourcery 交互
自定义你的使用体验访问你的 Dashboard 以:
获取帮助Original review guide in EnglishReviewer's GuideIntroduce an EnhancedEventBus that layers filterable and weak events on top of the existing EasyEvents/PriorityEvent infrastructure, adds pluggable event filters, and centralizes event statistics tracking, with comprehensive unit tests for weak events, filters, and statistics. Sequence diagram for sending a filterable event with predicate and sampling filters plus statisticssequenceDiagram
participant Client
participant EnhancedEventBus
participant FilterableEvent_T_
participant PredicateEventFilter_T_
participant SamplingEventFilter_T_
participant EventStatistics
participant Listener
Client->>EnhancedEventBus: RegisterFilterable_T_(onEvent)
EnhancedEventBus->>FilterableEvent_T_: GetOrAdd TestEvent
FilterableEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
FilterableEvent_T_-->>EnhancedEventBus: IUnRegister
EnhancedEventBus-->>Client: IUnRegister
Client->>EnhancedEventBus: AddFilter_T_(PredicateEventFilter_T_)
EnhancedEventBus->>FilterableEvent_T_: AddFilter(predicateFilter)
Client->>EnhancedEventBus: AddFilter_T_(SamplingEventFilter_T_)
EnhancedEventBus->>FilterableEvent_T_: AddFilter(samplingFilter)
Client->>EnhancedEventBus: SendFilterable_T_(data)
EnhancedEventBus->>FilterableEvent_T_: Trigger(data)
FilterableEvent_T_->>EventStatistics: RecordPublish(TestEvent)
%% Apply filters
FilterableEvent_T_->>PredicateEventFilter_T_: ShouldFilter(data)
alt PredicateFilter blocks
PredicateEventFilter_T_-->>FilterableEvent_T_: true
FilterableEvent_T_-->>EnhancedEventBus: return (event filtered)
else PredicateFilter passes
PredicateEventFilter_T_-->>FilterableEvent_T_: false
FilterableEvent_T_->>SamplingEventFilter_T_: ShouldFilter(data)
alt SamplingFilter blocks
SamplingEventFilter_T_-->>FilterableEvent_T_: true
FilterableEvent_T_-->>EnhancedEventBus: return (event sampled out)
else SamplingFilter passes
SamplingEventFilter_T_-->>FilterableEvent_T_: false
FilterableEvent_T_->>Listener: onEvent(data)
Listener-->>FilterableEvent_T_: handled
FilterableEvent_T_->>EventStatistics: RecordHandle()
end
end
Sequence diagram for sending a weak event with auto cleanup and statisticssequenceDiagram
participant Client
participant EnhancedEventBus
participant WeakEvent_T_
participant EventStatistics
participant AliveListener
participant DeadListener
Client->>EnhancedEventBus: RegisterWeak_T_(DeadListener.OnEvent)
EnhancedEventBus->>WeakEvent_T_: Register(DeadListener)
WeakEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
Client->>EnhancedEventBus: RegisterWeak_T_(AliveListener.OnEvent)
EnhancedEventBus->>WeakEvent_T_: Register(AliveListener)
WeakEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
Note over DeadListener: DeadListener becomes unreachable
Client->>Client: GC.Collect()
Client->>EnhancedEventBus: SendWeak_T_(data)
EnhancedEventBus->>WeakEvent_T_: Trigger(data)
WeakEvent_T_->>EventStatistics: RecordPublish(TestEvent)
WeakEvent_T_->>WeakEvent_T_: Collect alive handlers
WeakEvent_T_->>DeadListener: TryGetTarget()
DeadListener-->>WeakEvent_T_: collected (removed)
WeakEvent_T_->>AliveListener: TryGetTarget()
AliveListener-->>WeakEvent_T_: alive
WeakEvent_T_->>AliveListener: OnEvent(data)
AliveListener-->>WeakEvent_T_: handled
WeakEvent_T_->>EventStatistics: RecordHandle()
WeakEvent_T_->>EventStatistics: UpdateListenerCount(TestEvent)
Class diagram for EnhancedEventBus with filters, weak events, and statisticsclassDiagram
class EnhancedEventBus {
-EasyEvents _mEvents
-ConcurrentDictionary~Type, object~ _mFilterableEvents
-EasyEvents _mPriorityEvents
-ConcurrentDictionary~Type, object~ _mWeakEvents
-EventStatistics _statistics
+EnhancedEventBus(enableStatistics bool)
+IEventStatistics Statistics
+void Send_T_()
+void Send_T_(e T)
+void Send_T_(e T, propagation EventPropagation)
+IUnRegister Register_T_(onEvent Action_T_)
+IUnRegister Register_T_(onEvent Action_T_, priority int)
+void UnRegister_T_(onEvent Action_T_)
+IUnRegister RegisterWithContext_T_(onEvent Action_EventContext_T__)
+IUnRegister RegisterWithContext_T_(onEvent Action_EventContext_T__, priority int)
+void SendFilterable_T_(e T)
+IUnRegister RegisterFilterable_T_(onEvent Action_T_)
+void AddFilter_T_(filter IEventFilter_T_)
+void RemoveFilter_T_(filter IEventFilter_T_)
+void ClearFilters_T_()
+void SendWeak_T_(e T)
+IUnRegister RegisterWeak_T_(onEvent Action_T_)
+void CleanupWeak_T_()
}
class FilterableEvent_T_ {
-List~IEventFilter_T_~ _filters
-object _lock
-EventStatistics _statistics
-Action_T_ _onEvent
+FilterableEvent_T_(statistics EventStatistics)
+IUnRegister Register(onEvent Action_T_)
+void UnRegister(onEvent Action_T_)
+void Trigger(data T)
+void AddFilter(filter IEventFilter_T_)
+void RemoveFilter(filter IEventFilter_T_)
+void ClearFilters()
+int GetListenerCount()
}
class WeakEvent_T_ {
-object _lock
-EventStatistics _statistics
-List~WeakReference~Action_T_~~ _weakHandlers
+WeakEvent_T_(statistics EventStatistics)
+IUnRegister Register(onEvent Action_T_)
+void UnRegister(onEvent Action_T_)
+void Trigger(data T)
+void Cleanup()
+int GetListenerCount()
}
class EventStatistics {
-Dictionary~string, int~ _listenerCountByType
-Dictionary~string, long~ _publishCountByType
-object _lock
-int _activeEventTypes
-int _activeListeners
-long _totalFailed
-long _totalHandled
-long _totalPublished
+long TotalPublished
+long TotalHandled
+long TotalFailed
+int ActiveEventTypes
+int ActiveListeners
+long GetPublishCount(eventType string)
+int GetListenerCount(eventType string)
+void Reset()
+string GenerateReport()
+void RecordPublish(eventType string)
+void RecordHandle()
+void RecordFailure()
+void UpdateListenerCount(eventType string, count int)
}
class IEventStatistics {
<<interface>>
+long TotalPublished
+long TotalHandled
+long TotalFailed
+int ActiveEventTypes
+int ActiveListeners
+long GetPublishCount(eventType string)
+int GetListenerCount(eventType string)
+void Reset()
+string GenerateReport()
}
class IEventFilter_T_ {
<<interface>>
+bool ShouldFilter(eventData T)
}
class PredicateEventFilter_T_ {
-Func_T_, bool_ _predicate
+PredicateEventFilter_T_(predicate Func_T_, bool_)
+bool ShouldFilter(eventData T)
}
class SamplingEventFilter_T_ {
-double _samplingRate
-long _counter
+SamplingEventFilter_T_(samplingRate double)
+bool ShouldFilter(eventData T)
}
class IUnRegister {
<<interface>>
+void UnRegister()
}
class DefaultUnRegister {
-Action _onUnRegister
+DefaultUnRegister(onUnRegister Action)
+void UnRegister()
}
class IEventBus {
<<interface>>
}
class EasyEvents
class Event_T_
class PriorityEvent_T_
class EventContext_T_
EnhancedEventBus ..|> IEventBus
EventStatistics ..|> IEventStatistics
EnhancedEventBus --> EventStatistics : uses
EnhancedEventBus --> FilterableEvent_T_ : manages
EnhancedEventBus --> WeakEvent_T_ : manages
EnhancedEventBus --> EasyEvents : uses
EnhancedEventBus --> PriorityEvent_T_ : uses
FilterableEvent_T_ --> IEventFilter_T_ : aggregates
FilterableEvent_T_ --> EventStatistics : updates
WeakEvent_T_ --> EventStatistics : updates
PredicateEventFilter_T_ ..|> IEventFilter_T_
SamplingEventFilter_T_ ..|> IEventFilter_T_
FilterableEvent_T_ --> IUnRegister : returns
WeakEvent_T_ --> IUnRegister : returns
DefaultUnRegister ..|> IUnRegister
EasyEvents --> Event_T_ : manages
EasyEvents --> PriorityEvent_T_ : manages
PriorityEvent_T_ --> EventContext_T_
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了两个问题,并给出了一些整体性的反馈:
- 目前
EventStatistics的ActiveEventTypes和ActiveListeners从未被更新(具体实现类上的 setter 是 public 的但完全没有使用),因此这些属性将始终保持其默认值;建议在RecordPublish/UpdateListenerCount内更新它们,并将 setter 设为 private,以保证统计数据的一致性。 - 新增的统计目前只追踪
FilterableEvent和WeakEvent的流程;如果你的目标是提供全局的总线统计信息,你可能还需要在现有使用Event<T>和PriorityEvent<T>的Send/Register重载中集成EventStatistics,以避免统计数据被低估。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Currently `EventStatistics`'s `ActiveEventTypes` and `ActiveListeners` are never updated (and the setters on the concrete class are public but unused), so these properties will always stay at their default values; consider updating them inside `RecordPublish`/`UpdateListenerCount` and making the setters private to keep the statistics consistent.
- The new statistics only track `FilterableEvent` and `WeakEvent` flows; if the intent is to provide global bus statistics, you may want to also integrate `EventStatistics` into the existing `Send`/`Register` overloads that use `Event<T>` and `PriorityEvent<T>` to avoid under-reporting.
## Individual Comments
### Comment 1
<location path="GFramework.Core/events/EventStatistics.cs" line_range="15-24" />
<code_context>
+ private int _activeEventTypes;
</code_context>
<issue_to_address>
**issue (bug_risk):** ActiveEventTypes/ActiveListeners fields are never updated based on per-type stats, making them misleading.
These fields are only backed by `_activeEventTypes` and `_activeListeners`, which are never incremented/decremented anywhere in this class (including `UpdateListenerCount`); only `Reset` clears them. That means these properties remain at their default values unless set externally, so they don’t reflect the real aggregate state.
Given you already track `_listenerCountByType` and `_publishCountByType`, consider either computing these values on demand in the property getters from those dictionaries, or updating `_activeEventTypes`/`_activeListeners` inside `UpdateListenerCount` whenever per-type counts change. If neither is needed, it may be clearer to remove these fields and treat the metrics as computed, read-only values.
</issue_to_address>
### Comment 2
<location path="GFramework.Core/events/FilterableEvent.cs" line_range="59-33" />
<code_context>
+ /// 触发事件
+ /// </summary>
+ /// <param name="data">事件数据</param>
+ public void Trigger(T data)
+ {
+ // 记录发布统计
+ _statistics?.RecordPublish(typeof(T).Name);
+
+ // 应用过滤器
+ lock (_lock)
+ {
+ // 事件被过滤,不触发监听器
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Trigger performs multiple locks and runs filter predicates under the lock, which can increase contention and risk reentrancy issues.
`Trigger` currently:
1. Holds `_lock` while executing `_filters.Any(filter => filter.ShouldFilter(data))`, and
2. Reacquires `_lock` to snapshot `_onEvent`.
Executing arbitrary filter logic while holding the lock risks deadlocks/reentrancy if filters call back into this class, and the double locking increases contention.
Instead, under a single lock, snapshot `_filters` and `_onEvent` (e.g. `var filters = _filters.ToArray(); var handlers = _onEvent;`), then release the lock and run filters/handlers on those snapshots. This minimizes the critical section and avoids running user code under the lock while preserving behavior.
Suggested implementation:
```csharp
/// <summary>
/// 触发事件
/// </summary>
/// <param name="data">事件数据</param>
public void Trigger(T data)
{
// 记录发布统计
_statistics?.RecordPublish(typeof(T).Name);
Action<T>? handlers;
var filtersSnapshot = Array.Empty<IFilter<T>>();
// 在单个锁中快照过滤器和监听器
lock (_lock)
{
if (_filters.Count > 0)
{
filtersSnapshot = _filters.ToArray();
}
handlers = _onEvent;
}
// 在锁外执行过滤逻辑
for (var i = 0; i < filtersSnapshot.Length; i++)
{
if (filtersSnapshot[i].ShouldFilter(data))
return;
}
// 在锁外执行事件处理程序
handlers?.Invoke(data);
```
1. Ensure that `IFilter<T>` is the correct interface type for elements stored in `_filters`. If `_filters` uses a different generic interface or concrete type, replace `IFilter<T>` with that type.
2. If `System` or `System.Linq` is not already imported where `Array.Empty<T>()` or `ToArray()` are used, add the appropriate `using` directives at the top of the file:
- `using System;`
- `using System.Linq;`
3. If `_filters` is not a collection with `.Count` and `.ToArray()` (e.g. it might be an `IEnumerable<...>`), adjust the snapshot logic accordingly (e.g. `var filtersSnapshot = _filters?.ToArray() ?? Array.Empty<...>();` inside the lock).
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- Currently
EventStatistics'sActiveEventTypesandActiveListenersare never updated (and the setters on the concrete class are public but unused), so these properties will always stay at their default values; consider updating them insideRecordPublish/UpdateListenerCountand making the setters private to keep the statistics consistent. - The new statistics only track
FilterableEventandWeakEventflows; if the intent is to provide global bus statistics, you may want to also integrateEventStatisticsinto the existingSend/Registeroverloads that useEvent<T>andPriorityEvent<T>to avoid under-reporting.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Currently `EventStatistics`'s `ActiveEventTypes` and `ActiveListeners` are never updated (and the setters on the concrete class are public but unused), so these properties will always stay at their default values; consider updating them inside `RecordPublish`/`UpdateListenerCount` and making the setters private to keep the statistics consistent.
- The new statistics only track `FilterableEvent` and `WeakEvent` flows; if the intent is to provide global bus statistics, you may want to also integrate `EventStatistics` into the existing `Send`/`Register` overloads that use `Event<T>` and `PriorityEvent<T>` to avoid under-reporting.
## Individual Comments
### Comment 1
<location path="GFramework.Core/events/EventStatistics.cs" line_range="15-24" />
<code_context>
+ private int _activeEventTypes;
</code_context>
<issue_to_address>
**issue (bug_risk):** ActiveEventTypes/ActiveListeners fields are never updated based on per-type stats, making them misleading.
These fields are only backed by `_activeEventTypes` and `_activeListeners`, which are never incremented/decremented anywhere in this class (including `UpdateListenerCount`); only `Reset` clears them. That means these properties remain at their default values unless set externally, so they don’t reflect the real aggregate state.
Given you already track `_listenerCountByType` and `_publishCountByType`, consider either computing these values on demand in the property getters from those dictionaries, or updating `_activeEventTypes`/`_activeListeners` inside `UpdateListenerCount` whenever per-type counts change. If neither is needed, it may be clearer to remove these fields and treat the metrics as computed, read-only values.
</issue_to_address>
### Comment 2
<location path="GFramework.Core/events/FilterableEvent.cs" line_range="59-33" />
<code_context>
+ /// 触发事件
+ /// </summary>
+ /// <param name="data">事件数据</param>
+ public void Trigger(T data)
+ {
+ // 记录发布统计
+ _statistics?.RecordPublish(typeof(T).Name);
+
+ // 应用过滤器
+ lock (_lock)
+ {
+ // 事件被过滤,不触发监听器
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Trigger performs multiple locks and runs filter predicates under the lock, which can increase contention and risk reentrancy issues.
`Trigger` currently:
1. Holds `_lock` while executing `_filters.Any(filter => filter.ShouldFilter(data))`, and
2. Reacquires `_lock` to snapshot `_onEvent`.
Executing arbitrary filter logic while holding the lock risks deadlocks/reentrancy if filters call back into this class, and the double locking increases contention.
Instead, under a single lock, snapshot `_filters` and `_onEvent` (e.g. `var filters = _filters.ToArray(); var handlers = _onEvent;`), then release the lock and run filters/handlers on those snapshots. This minimizes the critical section and avoids running user code under the lock while preserving behavior.
Suggested implementation:
```csharp
/// <summary>
/// 触发事件
/// </summary>
/// <param name="data">事件数据</param>
public void Trigger(T data)
{
// 记录发布统计
_statistics?.RecordPublish(typeof(T).Name);
Action<T>? handlers;
var filtersSnapshot = Array.Empty<IFilter<T>>();
// 在单个锁中快照过滤器和监听器
lock (_lock)
{
if (_filters.Count > 0)
{
filtersSnapshot = _filters.ToArray();
}
handlers = _onEvent;
}
// 在锁外执行过滤逻辑
for (var i = 0; i < filtersSnapshot.Length; i++)
{
if (filtersSnapshot[i].ShouldFilter(data))
return;
}
// 在锁外执行事件处理程序
handlers?.Invoke(data);
```
1. Ensure that `IFilter<T>` is the correct interface type for elements stored in `_filters`. If `_filters` uses a different generic interface or concrete type, replace `IFilter<T>` with that type.
2. If `System` or `System.Linq` is not already imported where `Array.Empty<T>()` or `ToArray()` are used, add the appropriate `using` directives at the top of the file:
- `using System;`
- `using System.Linq;`
3. If `_filters` is not a collection with `.Count` and `.ToArray()` (e.g. it might be an `IEnumerable<...>`), adjust the snapshot logic accordingly (e.g. `var filtersSnapshot = _filters?.ToArray() ?? Array.Empty<...>();` inside the lock).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- 在EasyEventGeneric中添加GetListenerCount方法获取监听器数量 - 重新排列EnhancedEventBus中字段顺序以优化内存布局 - 为所有Send方法添加发布统计记录功能 - 实现事件处理器包装以添加统计功能,包括成功处理和失败记录 - 添加监听器数量统计更新机制,自动跟踪注册和注销事件 - 重构FilterableEvent中的过滤逻辑以减少锁持有时间 - 在PriorityEvent中添加GetListenerCount方法获取总监听器数量 - 移除EventStatistics中的过时字段并优化ActiveEventTypes和ActiveListeners计算 - 清理StatisticsEventDecorator中的并发问题和实现装饰器模式
- 将优先级事件的传播模式从 Continue 修改为 All - 确保事件能够正确传递到所有监听器
Summary by Sourcery
引入增强版事件总线,支持可过滤事件与弱事件,并可选地对事件发布与处理进行统计。
新特性:
EnhancedEventBus,支持标准、优先级、可过滤和弱事件的发布与订阅,并提供可选的统计功能。IEventFilter实现,包括基于谓词的过滤器和采样过滤器。测试:
Original summary in English
Summary by Sourcery
Introduce an enhanced event bus with filterable and weak events plus optional statistics for event publishing and handling.
New Features:
Tests: