Feat/state management core#132
Merged
Merged
Conversation
- 实现 Store 类作为集中式状态容器,默认支持状态归约和订阅通知 - 添加 IReadonlyStore、IStore、IReducer 等状态管理相关抽象接口 - 实现 StoreExtensions 扩展方法,提供 Select 和 ToBindableProperty 选择器功能 - 添加 StoreSelection 类,支持从完整状态树中投影局部状态视图 - 实现 StoreDispatchContext 和 StoreDispatchRecord 用于分发过程诊断 - 添加 IStoreMiddleware 中间件接口,支持在分发过程中插入日志和审计逻辑 - 实现完整的状态选择器和绑定属性桥接功能,便于现有 UI 代码复用 - 添加 Store 相关单元测试,覆盖状态归约、订阅通知和选择器桥接场景
- 新增 state-management 文档,介绍集中式状态容器方案 - 在 property 文档中补充与 Store 的使用边界说明 - 更新核心功能表格,添加状态管理条目链接 - 在 README 中增加 StateManagement 功能描述 - 添加状态管理相关接口到抽象层文档 - 提供 Store 与 BindableProperty 的选择指导原则
- 引入 StoreBuilder<TState> 支持模块化配置 reducer 和中间件 - 实现状态选择视图缓存机制提升性能 - 重构订阅管理使用精确订阅对象替代委托链 - 增强 SubscribeWithInitValue 方法防止状态变化遗漏 - 添加完整的状态管理文档示例和测试用例 - 更新接口定义支持新的构建器功能
审阅者指南引入一个新的集中式状态管理核心(Store),包括抽象、实现、扩展、测试和文档,并将其与现有的 Property、Command 以及文档结构集成,同时不破坏现有的 BindableProperty 用法。 带有中间件、reducer 和订阅者的 Store 派发流水线时序图sequenceDiagram
participant Caller
participant Store as Store_TState_
participant Middleware1 as Middleware1
participant Middleware2 as Middleware2
participant Reducer1 as Reducer1
participant Reducer2 as Reducer2
participant Subscriber as Subscriber
Caller->>Store: Dispatch_TAction_(action)
activate Store
Store->>Store: EnsureNotDispatching()
Store->>Store: Create StoreDispatchContext_TState_
Store->>Middleware1: Invoke(context, next)
activate Middleware1
Middleware1->>Middleware2: Invoke(context, next)
activate Middleware2
Middleware2->>Reducer1: Reduce(currentState, action)
activate Reducer1
Reducer1-->>Middleware2: nextState1
deactivate Reducer1
Middleware2->>Reducer2: Reduce(nextState1, action)
activate Reducer2
Reducer2-->>Middleware2: nextState2
deactivate Reducer2
Middleware2->>Store: update context.NextState, HasStateChanged
deactivate Middleware2
Middleware1-->>Store: after next()
deactivate Middleware1
Store->>Store: compare PreviousState vs NextState
alt state changed
Store->>Store: update _state and diagnostics
Store->>Store: SnapshotListenersForNotification()
Store-->>Subscriber: onValueChanged(newState)
else no change
Store-->>Caller: return
end
deactivate Store
Controller–Command–Model–Store UI 更新流程时序图sequenceDiagram
actor User
participant Controller as PlayerPanelController
participant Command as DamagePlayerCommand
participant Model as PlayerPanelModel
participant Store as Store_PlayerPanelState_
participant Selection as StoreSelection_PlayerPanelState_int_
participant View as UI
User->>Controller: OnDamageButtonClicked()
Controller->>Command: new DamagePlayerCommand(amount)
Controller->>Command: Execute()
activate Command
Command->>Model: GetModel_PlayerPanelModel_()
Command->>Store: Dispatch_DamagePlayerAction_(action)
deactivate Command
Store-->>Store: apply reducers
Store-->>Store: update PlayerPanelState
Store-->>Selection: OnStoreChanged(state)
Selection-->>View: onValueChanged(health)
Controller->>Model: subscribe to Name, Health, HealthPercent
Model->>Selection: GetOrCreateBindableProperty for each key
Selection-->>Controller: RegisterWithInitValue callbacks
Controller-->>View: initial render with current values
新状态管理核心(Store 及相关抽象)的类图classDiagram
direction LR
class IReadonlyStore_TState_ {
<<interface>>
+TState State
+IUnRegister Subscribe(listener)
+IUnRegister SubscribeWithInitValue(listener)
+void UnSubscribe(listener)
}
class IStore_TState_ {
<<interface>>
+void Dispatch(action)
}
class IStoreDiagnostics_TState_ {
<<interface>>
+int SubscriberCount
+Type LastActionType
+DateTimeOffset LastStateChangedAt
+StoreDispatchRecord_TState_ LastDispatchRecord
}
class IReducer_TState_TAction_ {
<<interface>>
+TState Reduce(currentState, action)
}
class IStoreMiddleware_TState_ {
<<interface>>
+void Invoke(context, next)
}
class IStateSelector_TState_TSelected_ {
<<interface>>
+TSelected Select(state)
}
class IStoreBuilder_TState_ {
<<interface>>
+IStoreBuilder_TState_ WithComparer(comparer)
+IStoreBuilder_TState_ AddReducer_TAction_(reducer)
+IStoreBuilder_TState_ AddReducerFunc_TAction_(reducer)
+IStoreBuilder_TState_ UseMiddleware(middleware)
+IStore_TState_ Build(initialState)
}
class Store_TState_ {
+TState State
+int SubscriberCount
+Type LastActionType
+DateTimeOffset LastStateChangedAt
+StoreDispatchRecord_TState_ LastDispatchRecord
+Store_TState_(initialState, comparer)
+IUnRegister Subscribe(listener)
+IUnRegister SubscribeWithInitValue(listener)
+void UnSubscribe(listener)
+void Dispatch_TAction_(action)
+Store_TState_ RegisterReducer_TAction_(reducer)
+Store_TState_ RegisterReducerFunc_TAction_(reducer)
+Store_TState_ UseMiddleware(middleware)
+StoreSelection_TState_TSelected_ GetOrCreateSelection_TSelected_(key, selector, comparer)
+StoreSelection_TState_TSelected_ GetOrCreateBindableProperty_TSelected_(key, selector, comparer)
+static StoreBuilder_TState_ CreateBuilder()
}
class StoreSelection_TState_TSelected_ {
+TSelected Value
+StoreSelection_TState_TSelected_(store, selector, comparer)
+IUnRegister Register(onValueChanged)
+IUnRegister RegisterWithInitValue(action)
+void UnRegister(onValueChanged)
}
class StoreBuilder_TState_ {
+StoreBuilder_TState_ WithComparer(comparer)
+StoreBuilder_TState_ AddReducer_TAction_(reducer)
+StoreBuilder_TState_ AddReducerFunc_TAction_(reducer)
+StoreBuilder_TState_ UseMiddleware(middleware)
+IStore_TState_ Build(initialState)
}
class StoreDispatchContext_TState_ {
+object Action
+Type ActionType
+TState PreviousState
+TState NextState
+bool HasStateChanged
+DateTimeOffset DispatchedAt
+StoreDispatchContext_TState_(action, previousState)
}
class StoreDispatchRecord_TState_ {
+object Action
+Type ActionType
+TState PreviousState
+TState NextState
+bool HasStateChanged
+DateTimeOffset DispatchedAt
+StoreDispatchRecord_TState_(action, previousState, nextState, hasStateChanged, dispatchedAt)
}
class StoreExtensions {
<<static>>
+StoreSelection_TState_TSelected_ Select_TState_TSelected_(store, selector)
+StoreSelection_TState_TSelected_ SelectWithComparer_TState_TSelected_(store, selector, comparer)
+StoreSelection_TState_TSelected_ SelectWithStateSelector_TState_TSelected_(store, selector, comparer)
+IReadonlyBindableProperty_TSelected_ ToBindableProperty_TState_TSelected_(store, selector, comparer)
}
class IReadonlyBindableProperty_TSelected_ {
<<interface>>
+TSelected Value
+IUnRegister Register(onValueChanged)
+IUnRegister RegisterWithInitValue(action)
+void UnRegister(onValueChanged)
}
class IUnRegister {
<<interface>>
+void UnRegister()
}
Store_TState_ ..> StoreDispatchContext_TState_ : uses
Store_TState_ ..> StoreDispatchRecord_TState_ : creates
Store_TState_ ..> IReducer_TState_TAction_ : uses
Store_TState_ ..> IStoreMiddleware_TState_ : uses
Store_TState_ ..> StoreSelection_TState_TSelected_ : creates
StoreSelection_TState_TSelected_ ..> IReadonlyStore_TState_ : reads
StoreExtensions ..> StoreSelection_TState_TSelected_ : creates
StoreSelection_TState_TSelected_ ..|> IReadonlyBindableProperty_TSelected_
Store_TState_ ..|> IStore_TState_
Store_TState_ ..|> IReadonlyStore_TState_
Store_TState_ ..|> IStoreDiagnostics_TState_
IStore_TState_ ..|> IReadonlyStore_TState_
StoreBuilder_TState_ ..|> IStoreBuilder_TState_
StoreDispatchRecord_TState_ <.. IStoreDiagnostics_TState_ : exposes
NoteForLegend <|-- Store_TState_ : concrete
NoteForLegend2 <|-- StoreSelection_TState_TSelected_ : concrete
文件级变更
技巧和命令与 Sourcery 交互
自定义你的体验访问你的控制面板 来:
获取帮助
Original review guide in EnglishReviewer's GuideIntroduce a new centralized state management core (Store) with abstractions, implementation, extensions, tests, and documentation, and integrate it with existing Property, Command, and documentation structure without breaking BindableProperty usage. Sequence diagram for Store dispatch pipeline with middleware, reducers, and subscriberssequenceDiagram
participant Caller
participant Store as Store_TState_
participant Middleware1 as Middleware1
participant Middleware2 as Middleware2
participant Reducer1 as Reducer1
participant Reducer2 as Reducer2
participant Subscriber as Subscriber
Caller->>Store: Dispatch_TAction_(action)
activate Store
Store->>Store: EnsureNotDispatching()
Store->>Store: Create StoreDispatchContext_TState_
Store->>Middleware1: Invoke(context, next)
activate Middleware1
Middleware1->>Middleware2: Invoke(context, next)
activate Middleware2
Middleware2->>Reducer1: Reduce(currentState, action)
activate Reducer1
Reducer1-->>Middleware2: nextState1
deactivate Reducer1
Middleware2->>Reducer2: Reduce(nextState1, action)
activate Reducer2
Reducer2-->>Middleware2: nextState2
deactivate Reducer2
Middleware2->>Store: update context.NextState, HasStateChanged
deactivate Middleware2
Middleware1-->>Store: after next()
deactivate Middleware1
Store->>Store: compare PreviousState vs NextState
alt state changed
Store->>Store: update _state and diagnostics
Store->>Store: SnapshotListenersForNotification()
Store-->>Subscriber: onValueChanged(newState)
else no change
Store-->>Caller: return
end
deactivate Store
Sequence diagram for Controller–Command–Model–Store UI update flowsequenceDiagram
actor User
participant Controller as PlayerPanelController
participant Command as DamagePlayerCommand
participant Model as PlayerPanelModel
participant Store as Store_PlayerPanelState_
participant Selection as StoreSelection_PlayerPanelState_int_
participant View as UI
User->>Controller: OnDamageButtonClicked()
Controller->>Command: new DamagePlayerCommand(amount)
Controller->>Command: Execute()
activate Command
Command->>Model: GetModel_PlayerPanelModel_()
Command->>Store: Dispatch_DamagePlayerAction_(action)
deactivate Command
Store-->>Store: apply reducers
Store-->>Store: update PlayerPanelState
Store-->>Selection: OnStoreChanged(state)
Selection-->>View: onValueChanged(health)
Controller->>Model: subscribe to Name, Health, HealthPercent
Model->>Selection: GetOrCreateBindableProperty for each key
Selection-->>Controller: RegisterWithInitValue callbacks
Controller-->>View: initial render with current values
Class diagram for new state management core (Store and related abstractions)classDiagram
direction LR
class IReadonlyStore_TState_ {
<<interface>>
+TState State
+IUnRegister Subscribe(listener)
+IUnRegister SubscribeWithInitValue(listener)
+void UnSubscribe(listener)
}
class IStore_TState_ {
<<interface>>
+void Dispatch(action)
}
class IStoreDiagnostics_TState_ {
<<interface>>
+int SubscriberCount
+Type LastActionType
+DateTimeOffset LastStateChangedAt
+StoreDispatchRecord_TState_ LastDispatchRecord
}
class IReducer_TState_TAction_ {
<<interface>>
+TState Reduce(currentState, action)
}
class IStoreMiddleware_TState_ {
<<interface>>
+void Invoke(context, next)
}
class IStateSelector_TState_TSelected_ {
<<interface>>
+TSelected Select(state)
}
class IStoreBuilder_TState_ {
<<interface>>
+IStoreBuilder_TState_ WithComparer(comparer)
+IStoreBuilder_TState_ AddReducer_TAction_(reducer)
+IStoreBuilder_TState_ AddReducerFunc_TAction_(reducer)
+IStoreBuilder_TState_ UseMiddleware(middleware)
+IStore_TState_ Build(initialState)
}
class Store_TState_ {
+TState State
+int SubscriberCount
+Type LastActionType
+DateTimeOffset LastStateChangedAt
+StoreDispatchRecord_TState_ LastDispatchRecord
+Store_TState_(initialState, comparer)
+IUnRegister Subscribe(listener)
+IUnRegister SubscribeWithInitValue(listener)
+void UnSubscribe(listener)
+void Dispatch_TAction_(action)
+Store_TState_ RegisterReducer_TAction_(reducer)
+Store_TState_ RegisterReducerFunc_TAction_(reducer)
+Store_TState_ UseMiddleware(middleware)
+StoreSelection_TState_TSelected_ GetOrCreateSelection_TSelected_(key, selector, comparer)
+StoreSelection_TState_TSelected_ GetOrCreateBindableProperty_TSelected_(key, selector, comparer)
+static StoreBuilder_TState_ CreateBuilder()
}
class StoreSelection_TState_TSelected_ {
+TSelected Value
+StoreSelection_TState_TSelected_(store, selector, comparer)
+IUnRegister Register(onValueChanged)
+IUnRegister RegisterWithInitValue(action)
+void UnRegister(onValueChanged)
}
class StoreBuilder_TState_ {
+StoreBuilder_TState_ WithComparer(comparer)
+StoreBuilder_TState_ AddReducer_TAction_(reducer)
+StoreBuilder_TState_ AddReducerFunc_TAction_(reducer)
+StoreBuilder_TState_ UseMiddleware(middleware)
+IStore_TState_ Build(initialState)
}
class StoreDispatchContext_TState_ {
+object Action
+Type ActionType
+TState PreviousState
+TState NextState
+bool HasStateChanged
+DateTimeOffset DispatchedAt
+StoreDispatchContext_TState_(action, previousState)
}
class StoreDispatchRecord_TState_ {
+object Action
+Type ActionType
+TState PreviousState
+TState NextState
+bool HasStateChanged
+DateTimeOffset DispatchedAt
+StoreDispatchRecord_TState_(action, previousState, nextState, hasStateChanged, dispatchedAt)
}
class StoreExtensions {
<<static>>
+StoreSelection_TState_TSelected_ Select_TState_TSelected_(store, selector)
+StoreSelection_TState_TSelected_ SelectWithComparer_TState_TSelected_(store, selector, comparer)
+StoreSelection_TState_TSelected_ SelectWithStateSelector_TState_TSelected_(store, selector, comparer)
+IReadonlyBindableProperty_TSelected_ ToBindableProperty_TState_TSelected_(store, selector, comparer)
}
class IReadonlyBindableProperty_TSelected_ {
<<interface>>
+TSelected Value
+IUnRegister Register(onValueChanged)
+IUnRegister RegisterWithInitValue(action)
+void UnRegister(onValueChanged)
}
class IUnRegister {
<<interface>>
+void UnRegister()
}
Store_TState_ ..> StoreDispatchContext_TState_ : uses
Store_TState_ ..> StoreDispatchRecord_TState_ : creates
Store_TState_ ..> IReducer_TState_TAction_ : uses
Store_TState_ ..> IStoreMiddleware_TState_ : uses
Store_TState_ ..> StoreSelection_TState_TSelected_ : creates
StoreSelection_TState_TSelected_ ..> IReadonlyStore_TState_ : reads
StoreExtensions ..> StoreSelection_TState_TSelected_ : creates
StoreSelection_TState_TSelected_ ..|> IReadonlyBindableProperty_TSelected_
Store_TState_ ..|> IStore_TState_
Store_TState_ ..|> IReadonlyStore_TState_
Store_TState_ ..|> IStoreDiagnostics_TState_
IStore_TState_ ..|> IReadonlyStore_TState_
StoreBuilder_TState_ ..|> IStoreBuilder_TState_
StoreDispatchRecord_TState_ <.. IStoreDiagnostics_TState_ : exposes
NoteForLegend <|-- Store_TState_ : concrete
NoteForLegend2 <|-- StoreSelection_TState_TSelected_ : concrete
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| C# | Mar 23, 2026 12:14p.m. | Review ↗ | |
| Secrets | Mar 23, 2026 12:14p.m. | Review ↗ |
There was a problem hiding this comment.
Hey - 我在这里给出了一些高层次的反馈:
- 在
Store<TState>.Dispatch中,目前的锁覆盖了整个 middleware + reducer 管线的执行;如果中间件或 reducer 执行较耗时的操作,这会使所有 dispatch 串行化并增加竞争。建议将临界区限制在对状态 / reducer 表的修改上,并在离开锁之前捕获运行管线所需的上下文,然后在锁之外执行管线(或者在文档中明确说明中间件必须保持非常轻量)。 - 新增的类型
Store<TState>、StoreSelection<TState, TSelected>和StoreBuilder<TState>暴露了较大的继承面,但看起来并不是为了扩展而设计的;建议将它们标记为sealed(并将某些实现细节层面的接口 / 类标记为internal),以缩小公共 API 的范围,并使未来的重构更安全。
面向 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- In `Store<TState>.Dispatch` the lock currently spans the entire middleware + reducer pipeline execution; if middlewares or reducers perform longer-running work this will serialize all dispatches and can increase contention, so consider limiting the critical section to state/reducer table mutations and capturing the needed context before running the pipeline outside the lock (or explicitly documenting that middlewares must remain very lightweight).
- The new types `Store<TState>`, `StoreSelection<TState, TSelected>` and `StoreBuilder<TState>` expose quite a large surface for inheritance but don’t appear to be designed for extension; consider marking them `sealed` (and potentially some implementation-detail interfaces/classes as `internal`) to keep the public API smaller and make future refactors safer.帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've left some high level feedback:
- In
Store<TState>.Dispatchthe lock currently spans the entire middleware + reducer pipeline execution; if middlewares or reducers perform longer-running work this will serialize all dispatches and can increase contention, so consider limiting the critical section to state/reducer table mutations and capturing the needed context before running the pipeline outside the lock (or explicitly documenting that middlewares must remain very lightweight). - The new types
Store<TState>,StoreSelection<TState, TSelected>andStoreBuilder<TState>expose quite a large surface for inheritance but don’t appear to be designed for extension; consider marking themsealed(and potentially some implementation-detail interfaces/classes asinternal) to keep the public API smaller and make future refactors safer.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `Store<TState>.Dispatch` the lock currently spans the entire middleware + reducer pipeline execution; if middlewares or reducers perform longer-running work this will serialize all dispatches and can increase contention, so consider limiting the critical section to state/reducer table mutations and capturing the needed context before running the pipeline outside the lock (or explicitly documenting that middlewares must remain very lightweight).
- The new types `Store<TState>`, `StoreSelection<TState, TSelected>` and `StoreBuilder<TState>` expose quite a large surface for inheritance but don’t appear to be designed for extension; consider marking them `sealed` (and potentially some implementation-detail interfaces/classes as `internal`) to keep the public API smaller and make future refactors safer.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- 将 Store 类标记为 sealed 以防止继承 - 引入独立的 dispatch 门闩锁,将状态锁的保护范围缩小为仅保护临界区访问 - 实现 dispatch 过程中的快照机制,确保中间件和 reducer 在锁外执行稳定的不可变序列 - 重构 ExecuteDispatchPipeline 方法,接受快照参数并改为静态方法 - 添加 CreateReducerSnapshot 方法为每次分发创建 reducer 快照 - 更新 StoreBuilder 和 StoreSelection 类为 sealed - 新增测试用例验证长时间运行的 middleware 不会阻塞状态读取和订阅操作 - 修复 dispatch 过程中状态锁占用时间过长的问题,提升并发性能
- 添加了属性抽象层的命名空间引用 - 添加了状态管理抽象层的命名空间引用 - 保持了原有的核心扩展、属性和状态管理命名空间引用
This was referenced Mar 28, 2026
This was referenced May 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary by Sourcery
引入集中式状态管理核心,包括 Store、reducer、中间件和选择工具,并将其集成到核心抽象层、文档和测试中。
New Features:
Store<TState>,提供 dispatch、reducers、中间件管线、选择结果缓存以及诊断功能。IReadonlyStore、IStore、IReducer、IStoreMiddleware、IStateSelector、IStoreBuilder,以及与 dispatch 相关的上下文/记录类型。StoreBuilder<TState>用于模块化地配置和构建 Store。StoreSelection<TState,TSelected>和StoreExtensions,将 Store 桥接为基于 selector 的视图以及类似 BindableProperty 风格的只读视图。Enhancements:
Tests:
Original summary in English
Summary by Sourcery
Introduce a centralized state management core with Store, reducers, middleware, and selection utilities, integrating it into the core abstractions, documentation, and tests.
New Features:
Enhancements:
Tests: