🎓 严老师 · C# / WPF 教学
← 全部课程 Day 1 首次教学 · 资深起点确认

> ⚠️ 严老师 Day 1 (2026-07-27) 在飞书实际推送为 0 条消息(可能是 p2…

0推送消息数
0字符数
2026-07-27教学日期
> ⚠️ 严老师 Day 1 (2026-07-27) 在飞书实际推送为 0 条消息(可能是 p2p chat 尚未建立,或当时推送到了别处)。以下为 MEMORY 留存的摘要便条,仅供索引。完整课堂原文未能恢复。 - **主题**:值类型 vs 引用类型(内存布局 / 装箱拆箱) - **起点水平确认**:C# 资深(主人主动告知,2026-07-27 16:50) - **深度策略**:跳过基础概念,直接开到底层(IL、JIT、GC、ref struct、Generic Math 等资深级内容) - **推送渠道**:飞书 DM(`teacher-yan-bot`),分 5 段推送成功 ### 2026-07-24(项目启动) - 主题:_项目初始化,等待首次教学_ - 起点水平:_默认"中级偏入门"_ → **已更正为"资深"** --- ## 📚 已教知识点索引 > 用于去重。每天推送前**必查**,跳过已教内容。 ### C# 语言基础 | # | 知识点 | 教学日 | 主人掌握度 | 备注 | |---|--------|--------|------------|------| | 1 | 值类型 vs 引用类型(内存布局 / 装箱拆箱 / 现代 .NET 演进) | 2026-07-27 | 待评估 | Day 1 内容,已推送 | | 2 | 泛型的本质(运行时擦除 vs 编译期特化 / 泛型共享 / MakeGenericType) | 2026-07-28 | 待评估 | Day 2 内容,已推送 | | 3 | 委托与事件(MulticastDelegate 内存布局 / 多播链 / 事件封装性 / 闭包 DisplayClass / CommandManager 泄漏) | 2026-08-01 | 待评估 | Day 3 内容,已推送 | | 4 | async/await 状态机(`IAsyncStateMachine` / MoveNext / SynchronizationContext / `ValueTask` / `IValueTaskSource`) | 2026-08-03 | 待评估 | Day 4 内容,已推送 | | 5 | LINQ 延迟执行与表达式树(`IEnumerable` vs `IQueryable` / `ExpressionVisitor` / EF Core SQL 翻译 / 强类型字段引用) | 2026-08-04 | 待评估 | Day 5 内容,已推送 | | 6 | 反射与元数据(CLR Metadata 40+ 表 / RID-token / `MethodInfo.Invoke` 装箱 / `Delegate.CreateDelegate` 强类型委托 / `Expression.Compile` IL 生成 / `[DynamicallyAccessedMembers]` AOT 注解 / `MetadataLoadContext`) | 2026-08-05 | 待评估 | Day 6 内容,已推送 | | 7 | Span / Memory(`ref struct` 零拷贝切片 / JIT 边界检查消除 / SIMD / `ArrayPool` 池化 / System.IO.Pipelines 5 层零分配架构 / WPF 高吞吐日志分析实战) | 2026-08-06 | 待评估 | Day 7 内容,已推送(**Phase 1 收官**) | ### .NET 运行时 / 底层 | # | 知识点 | 教学日 | 主人掌握度 | 备注 | |---|--------|--------|------------|------| | 8 | CLR 内存模型与 GC 机制(分代假设 / Gen0-1-2 / LOH 不压缩 / POH pinned 对象堆 / Workstation vs Server GC / Background GC / `GCSettings.LatencyMode` / `GC.TryStartNoGCRegion` / WPF 实时行情 5 层零 GC 架构) | 2026-08-07 | 待评估 | Day 8 内容,已推送(**Phase 2 开篇**) | | 9 | JIT 编译与 RyuJIT(双层编译 / RyuJIT vs 旧 x86 JIT / Tiered Compilation 0/1/2 / Dynamic PGO / ReadyToRun (R2R) / ProfileOptimization 多核并行 / WPF 冷启动 5 层优化架构) | 2026-08-08 | 待评估 | Day 9 内容,已推送(**Phase 2 第 2 课**) | | 10 | 异常处理机制(CIL Exception Handling Table / Windows SEH / 栈展开 / 零开销哲学 / `throw` vs `throw ex` / First/Second Chance Exception / ExceptionDispatchInfo / WPF 金融交易终端 5 层异常架构 / Try-Parse 模式 vs Result<T> vs OneOf) | 2026-08-09 | 待评估 | Day 10 内容,已推送(**Phase 2 第 3 课**) | | 11 | CLR 线程池与 TaskScheduler(`ThreadPool` 4 大哲学 / Work-Stealing 本地 LIFO + 偷取 FIFO / Hill Climbing 爬山算法 / Starvation Avoidance 主动注入 ~500ms 阈值 / `TaskScheduler` 解耦 / `TaskCreationOptions.LongRunning` 独立线程 / WPF DispatcherSynchronizationContext / 真异步 I/O 走 IOCP 不占 worker thread / 5 层并发架构 / 6 大反模式) | 2026-08-10 | 待评估 | Day 11 内容,已推送(**Phase 2 第 4 课**) | | 12 | 锁机制(`lock` / `Monitor` 语法糖 / `lockTaken` 异常安全 / `SemaphoreSlim` 异步信号量 / 自建 `AsyncLock` 生产模板 / `ReaderWriterLockSlim` 递归陷阱 / 对象头 Sync Block Index + Thin/Fat Lock 升级 / 内存屏障 acquire/release/full fence / `volatile` / `Monitor.Wait` 原子释放+阻塞 / `lock` 不能跨 await 3 大原因 / WPF 实时行情订阅 5 层架构) | 2026-08-11 | 待评估 | Day 12 内容,已推送(**Phase 2 第 5 课**) | | 13 | .NET 并行编程(`Parallel.For/ForEach` / `PLINQ` / `Channel<T>` / `TPL Dataflow` / `Task.WhenAll` 5 大原语对比 / 静态 vs 动态 vs Chunk 分区 / 数据并行 vs 流水线并行 / `BoundedChannelFullMode` 5 种背压策略 / `BoundedCapacity` + `PropagateCompletion` / WPF 5000 张 4K 图像 5 层流水线实战 + 5 大方案对比 + 内存控制 4 策略 + 性能预期 8x 加速比) | 2026-08-12 | 待评估 | Day 13 内容,已推送(**Phase 2 第 6 课**) | ### WPF 核心 | # | 知识点 | 教学日 | 主人掌握度 | 备注 | |---|--------|--------|------------|------| | 14 | WPF 架构总览(7 层程序集 / `WindowsBase`↔`PresentationCore`↔`PresentationFramework` 依赖关系 / XAML 编译原理 BAML / `InitializeComponent` 反射加载机制 / `Milcore` C++ COM 跨进程 / WPF 不能 NativeAOT 5 大根因 / R2R + 插件化 + 自包含部署替代方案) | 2026-08-13 | 待评估 | Day 14 内容,已推送(**Phase 3 开篇**) | | 15 | WPF 依赖属性(DP)核心(CLR 9 大局限 / `DependencyObject`+`EffectiveValueEntry[]` 懒分配 / 11 级值解析优先级 / Validate/Coerce/PropertyChanged 3 大回调执行顺序 / `RegisterReadOnly`+`DependencyPropertyKey` / `SetValue` vs `SetCurrentValue` / `OverrideMetadata` vs `AddOwner` / `RegisterAttached` 附加属性 / `PasswordStrengthIndicator` 完整生产级实战) | 2026-08-14 | 待评估 | Day 15 内容,已推送(**Phase 3 第 2 课**) | | 16 | WPF 路由事件(Routed Event)核心(CLR 事件 vs 路由事件本质区别 / 3 种路由策略 Bubble/Tunnel/Direct / `RoutedEventArgs.Source` vs `OriginalSource` / 视觉树 vs 逻辑树 / `EventManager.RegisterRoutedEvent` + `RaiseEvent` 完整 3 步生产代码 / `Handled` 机制 + `handledEventsToo: true` 父容器精确拦截 / 附加事件 vs 路由事件 / `EditableUserCard` 双模式 ListBox 实战 + 5 大生产陷阱) | 2026-08-15 | 待评估 | Day 16 内容,已推送(**Phase 3 第 3 课**) | | 17 | WPF 视觉树与逻辑树(两棵树正交分离 + `VisualTreeHelper` O(1) 字段查找 vs `LogicalTreeHelper.GetParent` O(深度) 递归 + `OnApplyTemplate` 唯一模板入口 + 主题切换重新实例化悬空引用 + `ConditionalWeakTable` FindAncestor 缓存 + `VisualBrush` 强制实时渲染双刃剑 + `InputHitTest` 命中测试 + `Ellipse.Tag` 绑业务数据 + `ChartCard` 数据可视化工具完整生产实战 + 5 大生产陷阱) | 2026-08-16 | 待评估 | Day 17 内容,已推送(**Phase 3 第 4 课**) | | 18 | WPF 布局系统(Layout System)核心(Measure/Arrange 两遍机制 / 4 个核心类 UIElement/FrameworkElement/Panel/Visual / 11 步布局管线 / DesiredSize vs RenderSize 5 维对比 / InvalidateMeasure vs InvalidateArrange 6 维对比 / 6 大内置 Panel 策略表 / 6 大常见误区 / WrapPanel 完整 300 行生产代码含 Margin/Orientation/换行算法 + 5 大生产陷阱 T1-T5 + 3 个扩展思考 FlexPanel 性能/浮点阈值/DPI 缩放 + 关联知识网络 DP/路由事件/视觉树/动画/绑定/自定义控件/DrawingVisual) | 2026-08-17 | 待评估 | Day 18 内容,已推送(**Phase 3 第 5 课**) | | 19 | WPF 资源系统核心(`ResourceDictionary` / `StaticResource` 值快照 / `DynamicResource` `ResourceReferenceExpression` 表达式订阅 / `MergedDictionaries` 单向合并后覆盖前 / 资源查找沿逻辑树向上深度优先 / `FindResource` vs `TryFindResource` 找不到行为差异 / 隐式样式 `Style TargetType="..."` 无 Key 自动应用 / ThemeService 动态皮肤切换生产代码 / 4 种方案对比 / 5 大生产陷阱 T1-T5 / 3 大性能优化 / 3 个动手扩展思考) | 2026-08-19 | 待评估 | Day 19 内容,已推送(**Phase 3 第 6 课**) | | 20 | WPF 数据绑定深入(Binding 核心机制 / 4 大 BindingMode + Default 二义性 / 5 大 BindingSource 8 维对比表 / `DataContext` 沿逻辑树单向继承 + `ItemsControl` 模板突变机制 / `Path` 完整语法 / **`TemplateBinding` 编译期优化压倒性优势**根因 / `IValueConverter` 5 实现要点 / `IMultiValueConverter` MultiBinding 5 陷阱 / `ValidationRule` 4 坑点 + `INotifyDataErrorInfo` 是企业级正解 / `INotifyPropertyChanged` 生产级模板含 `EqualityComparer<T>.Default`+`[CallerMemberName]`+静态 `PropertyChangedEventArgs` 缓存+跨线程 `Dispatcher.Invoke`+NaN 永远不等陷阱 / UserEditor 注册表单完整生产实战含 6 属性 BindingMode 决策表 / 5 大生产陷阱完整修复 T1-T5 / 3 个动手扩展思考 WeakDataContextChangedListener/Freezable 转换器/TypeDescriptionProvider) | 2026-08-20 | 待评估 | Day 20 内容,已推送(**Phase 3 第 7 课**) | | 21 | WPF 命令系统(`ICommand` 接口契约 / `ICommandSource` 触发源 / `CommandBinding` 处理器 / `RoutedCommand`+`RoutedUICommand` 路由引擎 / 5 大内置命令集合 `ApplicationCommands`/`NavigationCommands`/`MediaCommands`/`EditingCommands`/`ComponentCommands` / 命令路由沿视觉树冒泡找 `CommandBinding` / **`CommandManager.RequerySuggested` 进程级静态事件 6 大触发时机 + 内存泄漏根因完整强引用链图** / `RelayCommand` 完整生产级实现含不挂 `RequerySuggested` 切断静态泄漏链 / `AsyncRelayCommand` 含 `_isExecuting` 防重入 + `OperationCanceledException` 安静处理 + `Dispose` 取消 CTS / `UserEditViewModel` 完整生产实战含 `ObservableObject` 基类 `alsoNotifyCommands` 自动通知命令 / 5 大生产陷阱完整修复 T1-T5 / 7 大易错点 / 3 个动手扩展思考 `WeakRelayCommand`/`ICommand.Undo` 命令模式/Roslyn 模仿 CommunityToolkit `[RelayCommand]`) | 2026-08-21 | 待评估 | Day 21 内容,已推送(**Phase 3 第 8 课**) | | 22 | WPF 样式与模板系统(Style 4 大核心属性 TargetType/BasedOn/Resources/Setters + 选择器层级 隐式 vs 显式 vs 局部 + 优先级铁律 + ControlTemplate 5 大机制 PART_* 命名约定 + TemplateBinding 编译期优化 10-50x 根因 + ContentPresenter 自动应用 + OverridesDefaultStyle + DataTemplate 5 大机制 DataType 自动选择 + DataTemplateSelector 运行时选择 + HierarchicalDataTemplate 树形递归 + ItemTemplate 列表项 + ItemsPanelTemplate 4 大实战场景 + 4 大触发器 Trigger/MultiTrigger/DataTrigger/MultiDataTrigger/EventTrigger 完整对比 + Style Trigger vs VisualStateManager VSM 6 维辨析 + ValidationIndicator 完整生产代码 PART_* + 4 状态 DataTrigger + RelativeSource Self + Tooltip 动态更新 + 5 大生产陷阱 T1-T5 + 7 大易错点 + 3 个动手扩展思考 RatingControl/StepIndicator/TagInput + 8 道自检题) | 2026-08-22 | 待评估 | Day 22 内容,已推送(Phase 3 第 9 课) | | 23 | WPF 自定义控件开发(Custom Control vs UserControl 7 维对比 + 5 大核心价值可主题化/模板可替换/扩展性强/复用粒度最优/PART_* 统一约定 + 5 大核心概念 Themes/Generic.xaml + [assembly: ThemeInfo] 完整加载机制 + DefaultStyleKeyProperty.OverrideMetadata 自定义控件灵魂调用 + PART_* 命名约定设计师-开发者契约 + OnApplyTemplate 4 大铁律 base 优先/null 检查/GetTemplateChild 唯一入口/主题切换重新展开 + 自定义 DP 标准模板 AffectsRender+Coerce+PropertyChangedCallback+SetCurrentValue 不打断绑定 + 自定义路由事件标准模板 EventManager.RegisterRoutedEvent+Bubble+RoutedPropertyChangedEventArgs<bool> 带 OldValue/NewValue + OnRender 直接绘制 vs VisualHost+VisualCollection+DrawingVisual 6 维对比 + 控件继承链选择 4 维分析 + Themes/Generic.xaml 完整 ToggleSwitch 模板 PART_Rail+PART_Thumb+TemplateBinding IsChecked 编译期优化+TranslateTransform X 动画式 Trigger+IsEnabled Trigger Opacity + 5 大常见误区表 + 关联知识网络 Day 14-22 大串联 + 3 道递进面试题) | 2026-08-23 | 待评估 | Day 23 内容,已推送(Phase 4 第 4 课) | | 24 | WPF 动画系统(3 大动画分类 *Animation/*UsingKeyFrames/*AnimationUsingPath + Timeline 9 大子属性 BeginTime/Duration/RepeatBehavior/AutoReverse/SpeedRatio/AccelerationRatio/DecelerationRatio/FillBehavior + Storyboard 容器 + 4 大 KeyFrame Linear/Discrete/Spline/Easing + 11 大缓动函数族 Back/Bounce/Circle/Cubic/Elastic/Exponential/Power/Quadratic/Quartic/Quintic/Sine + EasingMode 3 选 1 EaseIn/EaseOut/EaseInOut + 5 大触发方式 EventTrigger/DataTrigger/Style.Triggers/Storyboard.Begin/VSM + FillBehavior HoldEnd vs Stop 完整对比 + **11 级 DP 优先级中动画位置 第 2 高 ⭐ 仅次于 Coerce 覆盖 LocalValue** + LoadingIndicator 完整生产代码含 4 大动画模式 + Themes/Generic.xaml + 5 大生产陷阱 T1-T5 + 7 大易错点 + 3 个动手扩展思考 CompositionTarget.Rendering 逐帧/RenderCapability.Tier 硬件加速/WPF 动画引擎 TimelineManager ClockGroup 源码) | 2026-08-24 | 待评估 | Day 24 内容,已推送(Phase 4 第 5 课) | | 25 | WPF UI 虚拟化(UI Virtualization)与大数据集合性能优化(4 大核心组件 VirtualizingPanel + IItemContainerGenerator + IScrollInfo + VirtualizationMode + 11 步完整工作流程 + Standard vs Recycling 3 维深度对比 + ScrollUnit Pixel vs Item + **完整 VariableHeightVirtualPanel 350 行生产代码**含 IScrollInfo 全部实现 + 与 DataGrid 内置虚拟化 7 维对比取舍 + 5 大生产陷阱 T1-T5 包裹 ScrollViewer 失效/ItemsPanel 写 StackPanel 失效/可变高度+ScrollUnit=Item 重排/Recycling 模式 INPC 缺失显示旧数据/虚拟化容器内 ElementName 引用失效 + 3 个动手扩展思考 数据虚拟化 + 自定义 VirtualizingWrapPanel + IL 层完整调用链 + 8 道自检题) | 2026-08-25 | 待评估 | Day 25 内容,已推送(Phase 4 第 6 课) | | 26 | WPF 拖放(Drag & Drop)系统核心(5 层架构分层业务层/WPF API 层/DataObject 层/OLE 层/OS 层 + DoDragDrop 静态方法启动器 + DataObject 数据容器 + DragEventArgs 事件载荷三角色矩阵 + 5 大内置标准格式 UnicodeText/Text/FileDrop CF_HDROP/Bitmap/Serializable + 与 WinForms 拖放 5 维本质对比 + DataObject 跨进程序列化机制同进程引用/跨进程字节流序列化/延迟序列化 + 自定义 DataObject 格式 RegisterFormat 必须否则 GetDataPresent 永远 false + 接收 Windows Explorer 文件拖入 CF_HDROP 自动填充 string[] + **完整 FileExplorerControl 280 行生产代码**含 FileTransferPayload[Serializable]+CustomFormats 静态构造 RegisterFormat+源端 3 事件+目标端 4 事件+DragAdorner 自定义视觉装饰器 IsHitTestVisible=false 关键 + 5 大生产陷阱 T1-T5 BinaryFormatter .NET 5+ 抛 SerializationException/虚拟化容器拖动源被回收 Recycle 模式/DragOver 重操作卡死 10-30ms 触发一次/RegisterFormat 跨进程不识别/DragAdorner 默认拦截鼠标 → 8 道自检题) | 2026-08-26 | 待评估 | Day 26 内容,已推送(Phase 4 第 7 课) | ### MVVM / 架构模式 | # | 知识点 | 教学日 | 主人掌握度 | 备注 | |---|--------|--------|------------|------| | - | _空_ | - | - | - | ### 性能 / 调试 / 最佳实践 | # | 知识点 | 教学日 | 主人掌握度 | 备注 | |---|--------|--------|------------|------| | - | _空_ | - | - | - | --- ## 🎯 主人掌握度总评 > 综合评估主人整体水平。每日更新。 - **当前评估**:**C# 资深**(主人 2026-07-27 16:50 主动确认) - **强项**:待 Day 1 反馈后评估 - **弱项**:待 Day 1 反馈后评估 - **教学节奏建议**: - ✅ 跳过基础语法 / 简单概念 - ✅ 直接进入 IL / JIT / GC / 底层机制 - ✅ 引入 .NET 5+ 现代特性(`ref struct`、`Generic Math`、`record struct`) - ✅ 面试题侧重"原理辨析"和"实战性能" - ✅ 代码示例贴近高频/企业级场景 --- ## 🛣️ 难度路径规划(草稿) > 严老师自己维护的可教知识点队列。每次推完一个,弹下一个。 ### Phase 1:C# 核心机制(4-6 周) - [x] 值类型 vs 引用类型(内存布局 / 装箱拆箱) — **2026-07-27 Day 1 完成** - [x] 泛型的本质(运行时擦除 vs 编译期特化) — **2026-07-28 Day 2 完成** - [x] 委托与事件(本质 / 内存模型 / 闭包) — **2026-08-01 Day 3 完成** - [x] async/await 状态机(IL 层 / SynchronizationContext / ValueTask / IValueTaskSource) — **2026-08-03 Day 4 完成** - [x] LINQ 延迟执行与表达式树(`IEnumerable` vs `IQueryable` / `ExpressionVisitor` / EF Core 翻译) — **2026-08-04 Day 5 完成** - [x] 反射与元数据(CLR Metadata / RID-token / Delegate.CreateDelegate / `[DynamicallyAccessedMembers]` / `MetadataLoadContext`) — **2026-08-05 Day 6 完成** - [x] Span / Memory 与高性能编程(`ref struct` / JIT 边界检查消除 / `ArrayPool` / System.IO.Pipelines) — **2026-08-06 Day 7 完成** ✅ **Phase 1 收官** ### Phase 2:.NET 运行时(3-4 周) - [x] CLR 内存模型(堆 / 栈 / GC 机制 / 代龄) — **2026-08-07 Day 8 完成** - [x] JIT 编译与 RyuJIT — **2026-08-08 Day 9 完成** - [x] 异常处理机制(栈展开 / SEH) — **2026-08-09 Day 10 完成** - [x] 线程池与 TaskScheduler — **2026-08-10 Day 11 完成** - [x] 锁机制(Monitor / SemaphoreSlim / AsyncLock) — **2026-08-11 Day 12 完成** - [x] 并行编程(Parallel / PLINQ / Channels) — **2026-08-12 Day 13 完成** ✅ **Phase 2 收官** ### Phase 3:WPF 基础(4-6 周) - [x] WPF 架构总览(PresentationFramework / PresentationCore / WindowsBase / System.Xaml / Milcore + XAML→BAML 编译 + InitializeComponent 反射 + 5 大不能 NativeAOT 根因) — **2026-08-13 Day 14 完成** ✅ **Phase 3 开篇** - [x] 依赖属性(DependencyProperty)核心(CLR 9 大局限 + 11 级优先级 + 3 大回调 + `RegisterReadOnly`+`DependencyPropertyKey` + `SetCurrentValue` + `PasswordStrengthIndicator` 实战) — **2026-08-14 Day 15 完成** - [x] 路由事件(Routed Event)(CLR 事件 vs 路由事件本质区别 + 3 种路由策略 Bubble/Tunnel/Direct + 完整传播顺序图 + Source vs OriginalSource 视觉树辨析 + Handled + handledEventsToo + 附加事件 + EditableUserCard 双模式 ListBox 实战 + 5 大生产陷阱) — **2026-08-15 Day 16 完成** - [x] 视觉树与逻辑树(两棵树正交分离 + VisualTreeHelper vs LogicalTreeHelper + OnApplyTemplate 唯一入口 + 主题切换 + FindAncestor + VisualBrush + InputHitTest + ChartCard 实战 + 5 大陷阱) — **2026-08-16 Day 17 完成** ✅ **Phase 3 第 4 课** - [x] 布局系统(Measure/Arrange 两遍机制 + 11 步管线 + DesiredSize vs RenderSize + InvalidateMeasure vs InvalidateArrange + 6 大内置 Panel 策略 + WrapPanel 完整生产代码 + 5 大生产陷阱 + 3 个扩展思考) — **2026-08-17 Day 18 完成** ✅ **Phase 3 第 5 课** - [x] 资源系统(`ResourceDictionary` + `StaticResource` 值快照 vs `DynamicResource` 表达式订阅 + `MergedDictionaries` 单向合并后覆盖前 + 资源查找沿逻辑树向上深度优先 + 隐式样式 `Style TargetType="..."` + ThemeService 动态皮肤切换 + 5 大生产陷阱 + 3 大性能优化) — **2026-08-19 Day 19 完成** ✅ **Phase 3 第 6 课** - [x] 数据绑定深入(Binding 核心机制 / 4 大 BindingMode + Default 二义性 / 5 大 BindingSource / `DataContext` 沿逻辑树继承 / **`TemplateBinding` 编译期优化** / `IValueConverter` 5 实现要点 / `IMultiValueConverter` MultiBinding / `ValidationRule` + `INotifyDataErrorInfo` / `INotifyPropertyChanged` 生产级模板 / UserEditor 注册表单完整生产实战 / 5 大生产陷阱 T1-T5 / 3 个动手扩展思考) — **2026-08-20 Day 20 完成** ✅ **Phase 3 第 7 课** - [x] 命令系统(`ICommand` 接口契约 / `ICommandSource` 触发源 / `CommandBinding` 处理器 / `RoutedCommand`+`RoutedUICommand` 路由引擎 / 5 大内置命令集合 / **`CommandManager.RequerySuggested` 进程级静态事件内存泄漏根因完整强引用链图** / `RelayCommand` 完整生产级实现含切断静态泄漏链 / `AsyncRelayCommand` 含防重入+取消+异常隔离 / `UserEditViewModel` 完整生产实战 / 5 大生产陷阱 T1-T5 / 7 大易错点 / 3 个动手扩展思考) — **2026-08-21 Day 21 完成(Phase 3 第 8 课)** - [x] 样式与模板(Style 4 大核心 / ControlTemplate 5 大机制 / DataTemplate 5 大机制 / ItemsPanelTemplate / TemplateBinding 编译期优化 / 4 大触发器 / ValidationIndicator 生产实战) — **2026-08-22 Day 22 完成 ✅** ### Phase 4:WPF 进阶(4-6 周) - [x] 数据绑定深入(Binding 核心机制 / 4 大 BindingMode / 5 大 BindingSource / TemplateBinding 编译期优化 / IValueConverter / ValidationRule / INotifyPropertyChanged 生产级模板 / UserEditor 完整生产实战) — **2026-08-20 Day 20 完成 ✅** - [x] 命令系统(ICommand / ICommandSource / CommandBinding / RoutedCommand / 5 大内置命令集合 / CommandManager.RequerySuggested 内存泄漏根因 / RelayCommand / AsyncRelayCommand / UserEditViewModel 完整实战) — **2026-08-21 Day 21 完成 ✅** - [x] 样式与模板(Style / ControlTemplate / DataTemplate / ItemsPanelTemplate) — **2026-08-22 Day 22 完成 ✅** - [x] 自定义控件开发(Custom Control vs UserControl 7 维对比 + 5 大核心价值 + 5 大核心概念 Themes/Generic.xaml + [assembly: ThemeInfo] 加载机制 + DefaultStyleKeyProperty.OverrideMetadata + PART_* 命名约定 + OnApplyTemplate 唯一入口 + 主题切换悬空引用 + 自定义 DP 标准模板 AffectsRender + Coerce + PropertyChangedCallback + 自定义路由事件标准模板 EventManager.RegisterRoutedEvent + RoutedPropertyChangedEventArgs + OnRender vs VisualHost+VisualCollection+DrawingVisual 性能对比 + 控件继承链选择 4 维分析 + Themes/Generic.xaml 完整 ToggleSwitch 模板 + 5 大常见误区表 + 3 道递进面试题) — **2026-08-23 Day 23 完成(Phase 4 第 4 课)✅** - [x] 动画系统(Storyboard / 缓动函数 / Animation Timeline / 关键帧动画 / 路径动画 / 动画性能优化)— **2026-08-24 Day 24 完成(Phase 4 第 5 课)✅** - [x] UI 虚拟化(UI Virtualization)+ 大数据集合性能优化(VirtualizingPanel + IItemContainerGenerator + IScrollInfo + VirtualizationMode Standard vs Recycling + ScrollUnit Pixel vs Item + 11 步完整工作流程 + VariableHeightVirtualPanel 350 行完整生产代码 + DataGrid 7 维对比取舍 + 5 大生产陷阱 T1-T5 + 3 个动手扩展思考 数据虚拟化 + VirtualizingWrapPanel + IL 层完整调用链 + 8 道自检题)— **2026-08-25 Day 25 完成(Phase 4 第 6 课)✅** - [ ] 渲染优化(DrawingVisual / VisualHost / RenderTargetBitmap / DrawingContext)— Phase 4 第 7 课候选 - [ ] Dispatcher 与线程亲和性(DispatcherObject / DispatcherPriority / BeginInvoke / InvokeAsync / 跨线程访问 / Invoke 死锁)— Phase 4 第 8 课候选 - [ ] 触摸笔迹(Touch / Stylus / InkCanvas / 手写识别 / 多点触控)— Phase 4 第 9 课候选 - [ ] 性能分析与诊断(PerfView / ETW / dotnet-trace / WPF 性能计数器 / 渲染线程 / GPU 加速)— Phase 4 第 10 课候选 - [ ] 国际化与本地化(.resx + x:Static + DynamicResource + XAML 资源切换)— Phase 4 第 11 课候选 - [ ] Phase 4 收官实战(综合应用所有 Phase 3+4 知识的大型项目)— Phase 4 收官 ### Phase 5:MVVM 架构(3-4 周) - [ ] INotifyPropertyChanged 正确实现 - [ ] ViewModel 基类设计 - [ ] 命令模式(RelayCommand / AsyncCommand) - [ ] 消息聚合器(Messenger / EventAggregator) - [ ] 依赖注入容器选择 - [ ] 模块化与导航 ### Phase 6:企业级实战(持续) - [ ] Prism 框架 - [ ] Caliburn.Micro - [ ] ReactiveUI / Rx.NET - [ ] 单元测试(Moq / FluentAssertions) - [ ] 性能分析与优化(dotTrace / PerfView) - [ ] 调试技巧(IntelliTrace / dumps) --- ## 💬 主人常问问题 > 记录主人反复提到的疑问,下次主动解答。 - _空_ --- ## 🚧 主人的卡点 > 主人反复卡住的点。下次出现时**优先**拆解。 - _空_ --- ## 📝 待办(教学相关) - [x] 首次会话:确认主人 C#/WPF 起点水平 — **已确认:资深**(2026-07-27) - [ ] 首次会话:确认周末 / 补救策略(待主人告知) - [x] 首次教学:从 Phase 1 第一个未教知识点开始 — **2026-07-27 完成 Day 1** - [x] **下次起点(已完成)**:Phase 1 第 5 个 — LINQ 延迟执行与表达式树 ✅ **2026-08-04 Day 5 推送完成** - [x] **下次起点(已完成)**:Phase 1 第 6 个 — 反射与元数据 ✅ **2026-08-05 Day 6 推送完成** - [x] **下次起点(已完成)**:Phase 1 第 7 个 — Span\<T\> / Memory\<T\> 与高性能编程 ✅ **2026-08-06 Day 7 推送完成(Phase 1 收官)** - [x] **下次起点(已完成)**:Phase 2 第 1 个 — CLR 内存模型与 GC 机制 ✅ **2026-08-07 Day 8 推送完成(Phase 2 开篇)** - [x] **下次起点(已完成)**:Phase 2 第 2 个 — JIT 编译与 RyuJIT ✅ **2026-08-08 Day 9 推送完成** - [x] **下次起点(已完成)**:Phase 2 第 3 个 — 异常处理机制(栈展开 / SEH)✅ **2026-08-09 Day 10 推送完成** - [x] **下次起点(已完成)**:Phase 2 第 4 个 — CLR 线程池与 TaskScheduler ✅ **2026-08-10 Day 11 推送完成** - [x] **下次起点(已完成)**:Phase 2 第 5 个 — 锁机制(`Monitor` / `SemaphoreSlim` / `AsyncLock` / 锁的内存模型语义 / 锁与 `CancellationToken` 集成 / `ReaderWriterLockSlim` 使用陷阱)✅ **2026-08-11 Day 12 推送完成** - [x] **下次起点(已完成)**:Phase 2 第 6 个 — 并行编程(`Parallel` / PLINQ / `Channel<T>` / TPL Dataflow / `TransformBlock` / `ActionBlock` / 背压流水线 / WPF 多线程图像处理实战)✅ **2026-08-12 Day 13 推送完成(Phase 2 收官)** - [x] **下次起点(已完成)**:Phase 3 第 1 个 — WPF 架构总览 ✅ **2026-08-13 Day 14 推送完成(Phase 3 开篇)** - [x] **下次起点(已完成)**:Phase 3 第 2 个 — 依赖属性(DependencyProperty)核心 ✅ **2026-08-14 Day 15 推送完成** - [x] **下次起点(已完成)**:Phase 3 第 3 个 — 路由事件(Routed Event)(CLR 事件 vs 路由事件本质区别 / 3 种路由策略 Bubble/Tunnel/Direct 执行顺序 / `RoutedEventArgs.Source` vs `OriginalSource` 区别 / `EventManager.RegisterRoutedEvent` + `RaiseEvent` / 附加事件 vs 路由事件 / 5 大使用陷阱)✅ **2026-08-15 Day 16 推送完成** - [x] **下次起点(已完成)**:Phase 3 第 4 个 — 视觉树与逻辑树 ✅ **2026-08-16 Day 17 推送完成** - [x] **下次起点(已完成)**:Phase 3 第 5 个 — 布局系统(Measure / Arrange 两遍机制 / `MeasureOverride` + `ArrangeOverride` / `Panel` 派生 / `DesiredSize` vs `RenderSize` / `InvalidateMeasure` vs `InvalidateArrange` / WrapPanel 完整生产代码 / 5 大陷阱)✅ **2026-08-17 Day 18 推送完成** - [x] **下次起点(已完成)**:Phase 3 第 6 个 — 资源系统(`ResourceDictionary` + `StaticResource` 值快照 vs `DynamicResource` 表达式订阅 + `MergedDictionaries` 单向合并后覆盖前 + 资源查找沿逻辑树向上深度优先 + 隐式样式 `Style TargetType="..."` + ThemeService 动态皮肤切换 + 5 大生产陷阱 + 3 大性能优化)✅ **2026-08-19 Day 19 推送完成(Phase 3 第 6 课)** - [x] **下次起点(已完成)**:Phase 3 第 7 个 — 数据绑定深入(Binding 核心机制 + 4 大 BindingMode + 5 大 BindingSource + TemplateBinding 编译期优化 + IValueConverter/IMultiValueConverter + ValidationRule + INotifyPropertyChanged 生产级模板 + UserEditor 完整生产实战 + 5 大生产陷阱)✅ **2026-08-20 Day 20 推送完成(Phase 3 第 7 课)** - [x] **下次起点(已完成)**:Phase 3 第 8 个 — 命令系统(`ICommand` 接口契约 / `ICommandSource` / `CommandBinding` / `RoutedCommand` / 5 大内置命令集合 / **`CommandManager.RequerySuggested` 内存泄漏根因完整强引用链图** / `RelayCommand` 生产级实现 / `AsyncRelayCommand` 含防重入+取消+异常隔离 / `UserEditViewModel` 完整实战 / 5 大生产陷阱 / 7 大易错点 / 3 个动手扩展思考) ✅ **2026-08-21 Day 21 推送完成(Phase 3 第 8 课)** - [x] **下次起点(已完成)**:Phase 3 第 10 个 — 自定义控件开发(Custom Control vs UserControl 7 维对比 + 5 大核心价值 + 5 大核心概念 Themes/Generic.xaml + [assembly: ThemeInfo] 加载机制 + DefaultStyleKeyProperty.OverrideMetadata + PART_* 命名约定 + OnApplyTemplate 唯一入口 + 主题切换悬空引用 + 自定义 DP 标准模板 AffectsRender + Coerce + PropertyChangedCallback + 自定义路由事件标准模板 EventManager.RegisterRoutedEvent + RoutedPropertyChangedEventArgs + OnRender 直接绘制 vs VisualHost+VisualCollection+DrawingVisual 性能对比 + 控件继承链选择 4 维分析 + Themes/Generic.xaml 完整 ToggleSwitch 模板 PART_Rail + PART_Thumb + TemplateBinding IsChecked 编译期优化 + TranslateTransform X 动画式 Trigger 开关切换 + 5 大常见误区表 + 关联知识网络 Day 14-22 大串联 + 3 道递进面试题) ✅ **2026-08-23 Day 23 推送完成(Phase 4 第 4 课)** - [x] **下次起点(已完成)**:Phase 4 第 5 个 — 动画系统(3 大分类 *Animation/*UsingKeyFrames/*AnimationUsingPath + Timeline 9 大子属性 + Storyboard 容器 + 4 大 KeyFrame Linear/Discrete/Spline/Easing + 11 大缓动函数族 + EasingMode 3 选 1 + 5 大触发方式 + FillBehavior HoldEnd vs Stop + **11 级 DP 优先级中动画位置 第 2 高** + 完整 LoadingIndicator 生产代码含 4 大动画模式 + 5 大生产陷阱 + 3 个动手扩展思考)✅ **2026-08-24 Day 24 推送完成(Phase 4 第 5 课)** - [x] **下次起点(已完成)**:Phase 4 第 6 个 — UI 虚拟化(UI Virtualization)+ 大数据集合性能优化(VirtualizingPanel + IItemContainerGenerator + IScrollInfo + VirtualizationMode Standard vs Recycling + ScrollUnit Pixel vs Item + 11 步完整工作流程 + VariableHeightVirtualPanel 350 行完整生产代码 + DataGrid 7 维对比取舍 + 5 大生产陷阱 T1-T5 + 3 个动手扩展思考 数据虚拟化 + VirtualizingWrapPanel + IL 层完整调用链 + 8 道自检题)✅ **2026-08-25 Day 25 推送完成(Phase 4 第 6 课)** - [ ] **下次起点(当前)**:Phase 4 第 7 课候选 — **渲染优化(DrawingVisual / VisualHost / RenderTargetBitmap / DrawingContext / RenderCapability.Tier / 合成器线程 / BitmapCache)** —— 直接承接今日 UI 虚拟化讲解 100 万行流畅后"为什么这么快"的渲染层根因 --- ## 📊 Day 23 教学记录(详细) ### 今日主题 WPF 自定义控件开发(Custom Control)核心(Custom Control vs UserControl 7 维对比 + 5 大核心价值 + 5 大核心概念 Themes/Generic.xaml/[assembly: ThemeInfo]/DefaultStyleKeyProperty.OverrideMetadata/PART_*/OnApplyTemplate + 自定义 DP 标准模板 + 自定义路由事件标准模板 + OnRender vs VisualHost+D中 DrawingVisual 性能对比 + 控件继承链选择 4 维分析 + Themes/Generic.xaml 完整 ToggleSwitch 模板 + 5 大常见误区表 + 关联知识网络 Day 14-22 大串联) ### 教学重点 - **核心**:自定义控件是 WPF 进阶的"金字塔尖"——综合运用 Day 14-22 学的所有知识点(架构、DP、路由事件、视觉树、资源、样式与模板、绑定、命令) - **本质区别(7 维对比)**: - 继承链:UserControl(具体类)/ CustomControl(Control 等通用基类) - 代码位置:内嵌 XAML + 强耦合 / Themes/Generic.xaml 独立 - 主题化:❌ 不能 / ✅ 天然支持(Light/Dark/HighContrast) - 复用粒度:实例级 / 类型级 - 来扩展:复制粘贴 / 派生 + 添加新 DP/路由事件 - 典型场景:业务组件组合 / UI 原语可换肤 - 模板位置:UserControl.xaml 内嵌 / Themes/Generic.xaml 独立字典 - **一句话本质**:用户控件 = "组合"(Composition,强类型 XAML 黑盒)vs 自定义控件 = "派生"(Inheritance,可主题化可换肤可扩展的 UI 原语) - **关键洞察**:WPF 所有内置控件(Button/TextBox/ListBox/DataGrid)**全部**是"自定义控件"——没有内置 UserControl,那是 WPF 团队为了便利业务代码组合提供的便利类 - **5 大核心价值**: 1. **可主题化**(Themability)—— Themes/Generic.xaml 字典替换,控件代码零修改 2. **模板可替换**(Template Replaceable)—— 同一 ToggleSwitch 可圆形/方块/滑动,行为不变外观千变万化 3. **扩展性极强**(Extensibility)—— 派生新控件只需添加新 DP + 新 RoutedEvent + 新 CommandBinding 4. **复用粒度最优**(Reusable Across Apps)—— 写一次 100 个项目引用 5. **统一 PART_* 约定**(Designer-Developer Collaboration)—— 设计师改模板 XAML 时他知道 PART_Thumb/PART_Rail 不能改名,设计师和开发者并行工作互不阻塞 - **5 大核心概念**: - **Themes/Generic.xaml + [assembly: ThemeInfo]** —— 资源字典文件位置 + resourceDictionaryLocation/resourceDictionarySourceName 两个参数;不写也行(默认找 Themes/Generic.xaml),但显式声明 2 大好处 IDE 感知 + AOT 追踪 - **DefaultStyleKeyProperty.OverrideMetadata** —— 自定义控件的"灵魂调用",不写它 Generic.xaml 永远找不到你的控件;每个 Control 派生类必须 Override 一份,否则都拿父类 Control 的样式 - **PART_* 命名约定 + OnApplyTemplate 4 大铁律** —— base() 优先 + null 检查清理旧订阅 + GetTemplateChild 唯一有效时机 + 主题切换再次调用 - **自定义 DP 标准模板** —— AffectsRender 标志 + CoerceValueCallback 防御性 + PropertyChangedCallback 触发视觉更新 + SetCurrentValue 不打断绑定的关键 - **自定义路由事件标准模板** —— EventManager.RegisterRoutedEvent(Bubble) + RoutedPropertyChangedEventArgs<T> 带 OldValue/NewValue + 业务事件和 UI 事件分离(CheckedChanged + Click) - **OnRender vs VisualHost + VisualCollection + DrawingVisual 6 维对比**:使用门槛/命中测试/鼠标事件/重绘成本/性能/适用场景;选择铁律——简单控件 OnRender / 频繁更新 VisualHost;永远不要混用 - **Themes/Generic.xaml 完整 ToggleSwitch 模板设计 3 要点**:PART_Rail + PART_Thumb 严格命名契约 / TemplateBinding IsChecked 编译期优化(比 {Binding RelativeSource=TemplatedParent} 快 10-50 倍)/ TranslateTransform X 触发 Trigger 动画式切换 - **控件继承链选择 4 维分析**:FrameworkElement(纯绘制无焦点)/ Control(标准可点击)/ HeaderedItemsControl(带 Header 的树)/ ItemsControl + Selector(列表可选择);铁律——继承链尽量浅 - **5 大常见误区表**:忘 DefaultStyleKeyProperty.OverrideMetadata 显示空白方块 / UserControl.xaml 写 XAML 失去主题化 / OnApplyTemplate 外 GetTemplateChild 返回 null / PART_* 名字拼写不一致 / 自定义 DP 设 AffectsRender 没 PropertyChangedCallback - **关联知识网络(Day 14-22 大串联)**: - Day 14 WPF 架构 → [assembly: ThemeInfo] + Milcore 加载 ResourceDictionary - Day 15 DP → 自定义 DP 注册 + Coerce + AffectsRender 标志 - Day 16 路由事件 → EventManager.RegisterRoutedEvent + RaiseEvent + RoutedPropertyChangedEventArgs - Day 17 视觉树 → OnApplyTemplate 唯一 GetTemplateChild 入口 + 主题切换重新展开 - Day 18 布局 → FrameworkElement.MeasureOverride / ArrangeOverride(非默认布局需要重写) - Day 19 资源系统 → Generic.xaml 主题字典 + MergedDictionaries(多个主题合并) - Day 20 数据绑定 → TemplateBinding 编译期优化(ControlTemplate 内用) - Day 21 命令系统 → 自定义 ICommand + CommandBinding(ToggleSwitch 暴露 ToggleCommand) - Day 22 样式与模板 → TargetType={x:Type local:ToggleSwitch} 应用本控件样式 + PART_* 命名约定 + DataTrigger/Trigger - **3 道递进面试题**: - Q1(基础概念):7 维对比表完整 + 一句话本质 + Button/TextBox/ListBox 都是 CustomControl 洞察 + 3 个具体场景 - Q2(原理与辨析):Themes/Generic.xaml 7 步加载流程 + DefaultStyleKey 作用 + PART_* 4 大作用 + 主题切换 5 步完整流程 + 不清旧订阅内存泄漏根因(闭包 + 累积 N 个) - Q3(实战与深度):完整 ToggleSwitch 生产代码 + Themes/Generic.xaml + OnRender vs DrawingVisual 选择 + 5 大常见误区完整错误示例与修复 - **周日特殊处理**:今天是周日(2026-08-23),主人可能在休息,已经告知"主人回答完三个题后再发完整复盘讲解,如果要先看复盘告诉我" ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 动画系统(Storyboard / 缓动函数 / Animation Timeline / 关键帧动画 / 路径动画 / 动画性能优化 / WPF 实时数据可视化动画实战)—— Day 24 --- ## 📊 Day 21 教学记录(详细) ### 今日主题 WPF 命令系统(Command System)核心(`ICommand` / `ICommandSource` / `CommandBinding` / `RoutedCommand` / `RelayCommand` / `AsyncRelayCommand` / `CommandManager.RequerySuggested` 内存泄漏根因 / MVVM `UserEditViewModel` 完整生产实战) ### 教学重点 - **核心**:命令系统是连接 UI 操作和业务逻辑的桥梁——4 大核心抽象(`ICommand` 契约 / `ICommandSource` 触发源 / `CommandBinding` 处理器 / `RoutedCommand` 路由引擎)+ 4 大价值(单一入口/状态统一/输入手势统一/MVVM 解耦) - **本质区别**:命令系统 ≠ 路由事件高级版;事件=通知(fire-and-forget),命令=请求-响应(CanExecute 反复被问 + Execute 一次性触发) - **5 大内置命令集合**:`ApplicationCommands`(剪贴板/保存/打开)/ `NavigationCommands`(浏览/前进后退)/ `MediaCommands`(播放控制)/ `EditingCommands`(文本编辑)/ `ComponentCommands`(滚动/移动焦点),共 100+ 标准命令 - **`CommandManager.RequerySuggested` 内存泄漏根因完整强引用链**(与 Day 3 委托事件泄漏一脉相承): ``` GC Root: static CommandManager ↓ 强引用 RequerySuggested: MulticastDelegate ↓ _invocationList CanExecuteChanged 委托 ↓ Target RelayCommand 实例 ↓ _execute 字段 Action lambda → 闭包 DisplayClass → ViewModel 实例 → 整个 View 子树 ``` 关闭 Window 后 ViewModel 还活着 → N 次打开关闭累积 N 个 ViewModel - **4 种解决方案对比**(A 手动通知切断静态引用链 ⭐推荐 / B WeakEventManager 子类复杂 / C WeakReference 包 CommandManager 不可行 / D CommunityToolkit 源生成器新项目首选) - **`RelayCommand` 完整生产级代码**: - 不挂 `CommandManager.RequerySuggested`(切断静态引用链) - 维护自己的 `CanExecuteChanged` 事件 + `RaiseCanExecuteChanged()` 方法 - 无参重载(`Action` / `Func<bool>`)和带参重载(`Action<object?>` / `Predicate<object?>`) - **`AsyncRelayCommand` 三铁律**: - **防重入**:`_isExecuting` 标志位 + `CanExecute` 返回 false - **异常隔离**:`try/catch (OperationCanceledException)` 安静 + `catch (Exception)` 兜底 - **资源释放**:`Dispose` 取消 `CancellationTokenSource` - **`ObservableObject` 基类支持 `alsoNotifyCommands`**:属性变化时连带触发命令刷新(不挂全局的关键补丁) - **Q3 完整 `UserEditViewModel` 生产代码**:Name/Age/Status/IsBusy 4 属性 + SaveCommand 同步含 canExecute / LoadCommand 异步含 IsBusy 联动 / XAML `UpdateSourceTrigger=PropertyChanged` 让 TextBox 每次输入触发 `Name` 变化 → 自动联动 SaveCommand - **5 大生产陷阱**:T1 RaiseCanExecuteChanged 写在 await 后 UI 不立即禁用 / T2 异步命令忘 catch OperationCanceledException 进程崩溃 / T3 窗口关闭未取消异步命令 ObjectDisposedException / T4 CanExecute 内 int.Parse 抛异常 WPF 静默吞 / T5 RelayCommand 挂 RequerySuggested 经典内存泄漏 - **7 大易错点**:RequerySuggested 忘 Dispose 泄漏 / ViewModel IDisposable 未 Dispose / async void 未捕获异常 / CanExecute 耗时操作 / CommandParameter 虚拟化离屏 null / 同步 RelayCommand 内 Task.Wait 死锁 / ApplicationCommands 未注册静默失败 - **3 个动手扩展思考**:WeakRelayCommand(WeakReference 包 ViewModel)/ ICommand.Undo 命令模式(Ctrl+Z 撤销)/ Roslyn Analyzers 模仿 CommunityToolkit [RelayCommand] - **关联知识网络**: - Day 14 WPF 架构:CommandManager 在 PresentationFramework.dll - Day 15 DP:ICommandSource.Command/CommandParameter/CommandTarget 都是 DP - Day 16 路由事件:ExecutedRoutedEventArgs 是路由事件成员 - Day 17 两棵树:**RoutedCommand 沿视觉树冒泡找 CommandBinding**(不是逻辑树) - Day 20 数据绑定:{Binding SaveCommand} 把 ViewModel.ICommand 绑到 Button.Command - 纵向连接:命令系统是 Day 16 路由事件的"业务语义升级版"——事件告诉"发生了什么",命令告诉"用户想要做什么 + 现在能做吗 + 谁能做" ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 样式与模板(Style / ControlTemplate / DataTemplate / ItemsPanelTemplate / TemplateBinding / 4 大触发器)—— Day 22 --- ## 📊 Day 4 教学记录(详细) ### 今日主题 async/await 状态机(编译器生成的 `IAsyncStateMachine` / `MoveNext` / `SynchronizationContext` 调度) ### 教学重点 - **核心**:async/await 是纯编译器语法糖——async 方法被重写为实现 `IAsyncStateMachine` 的 struct,await 拆分成 switch-case 分支 + 续作注册 - **状态机结构**:`<>1__state`(状态编号:-2=完成 / -1=初始 pending / 0~N=各 await 续作入口)、`<>t__builder`(AsyncTaskMethodBuilder 桥)、awaiter 字段、捕获变量字段 - **关键差异**: - `AwaitUnsafeOnCompleted` vs `AwaitOnCompleted`(前者避免类型检查,更快) - `SynchronizationContext` 跨平台抽象 vs `TaskScheduler` Task 专用 - `ConfigureAwait(false)` 的 IL 本质:只是 `continueOnCapturedContext` bool 参数 - **进阶武器**:`ValueTask<T>`(同步完成零分配)/`IValueTaskSource<T>`(池化 Task 源)/`Channel<T>`(背压解耦)/`CancellationToken`(协作式取消) - **Q3 实战 WPF 实时数据 5 层架构**: - 网络层:`IValueTaskSource<byte>` 池化 + `ArrayPool<byte>` 缓冲 - 接收层:`Channel<T>` + `ConfigureAwait(false)` + 后台线程循环 - 业务层:解析 + 节流(60fps 限速) - 调度层:`Dispatcher.BeginInvoke` + `CompositionTarget.Rendering` 帧对齐 - UI 层:`ObservableCollection` + `VirtualizingStackPanel.Recycling` ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 LINQ 延迟执行与表达式树 —— Day 5 --- ## 📊 Day 1 教学记录(详细) ### 今日主题 值类型 vs 引用类型(内存布局 / 装箱拆箱) ### 教学重点 - 内存布局本质:栈 / 堆 / 内联 / 引用语义 - IL 层 `box` / `unbox` / `unbox.any` 指令剖析 - 现代 .NET 演进:泛型特化、`Span<T>`、`record struct`、`Generic Math`、`in` / `readonly struct` - Q3 实战:高频 tick 系统中的装箱点排查与 3 种优化方案(`List<T>` / `Span<T>+ArrayPool` / `INumber<T>`) ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 泛型的本质(运行时擦除 vs 编译期特化)—— Day 2 (✅ 2026-07-28 完成) --- ## 📊 Day 2 教学记录(详细) ### 今日主题 泛型的本质(运行时擦除 vs 编译期特化) ### 教学重点 - **核心对比**:.NET 编译期特化 + 运行时独立类型 vs Java 编译期类型擦除 - **运行时机制**: - 开放泛型类型定义(`List<>`)vs 封闭泛型类型(`List<int>`)的 MethodTable 差异 - 泛型共享(Generic Sharing):引用类型共享 JIT 代码、值类型独立 JIT - 泛型约束的 CLR 支持范围(`class` / `struct` / `new()` / 基类 / 接口 vs 枚举/运算符约束) - **关键 API**:`typeof`、`MakeGenericType` / `MakeGenericMethod`、`default(T)`、协变 / 逆变 - **Q3 实战**:WPF 高性能事件总线 - 原 `Dictionary<Type, List<object>>` 实现的 4 大性能问题(装箱 / 虚调用 / 缓存局部性 / 锁竞争) - 重构为分层类型特化:`static class EventBus<T> { static List<Action<T>> _subscribers; }` - 测量:典型 5-10 倍性能提升 - 两大局限:静态字段内存泄漏 + Dictionary 缓存局部性差 - 改进方案:`WeakReference<T>` + `FrozenDictionary`(.NET 8+) ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 委托与事件(本质 / 内存模型 / 闭包)—— Day 3 --- ## 📊 Day 3 教学记录(详细) ### 今日主题 委托与事件(本质 / 内存模型 / 闭包) ### 教学重点 - **委托本质**:编译器生成 `MulticastDelegate` 派生密封类,含 `_target` / `_methodPtr` / `_invocationList` 三字段 - **多播链**:`Delegate.Combine(a, b)` 创建**新**委托实例,`_invocationList` 是 `Delegate[]`;`a += b` 不修改 `a` - **事件封装性**:`event` 是私有委托字段 + 编译器生成 `add`/`remove` 访问器(`Interlocked.CompareExchange` 线程安全);外部只能 `+=`/`-=`,不能 `=` 整体赋值或 `null` 清空 - **闭包陷阱**:所有被捕获局部变量提升到嵌套类 `<>c__DisplayClass` 的实例字段,每次方法调用都新建 DisplayClass + Action 实例(隐藏分配) - **闭包循环陷阱**:循环中 `for (int i = 0; i < 3; i++) actions.Add(() => i)` 输出 `3 3 3`,需每次迭代拷贝变量 - **Action\<T\> 等价性**:`Action<int>` 与自定义 `delegate void Foo(int x)` 在 IL 层完全相同,只是避免类型膨胀 + 协变逆变预定义 - **Q3 实战 RelayCommand 泄漏**: - **根因**:`CommandManager.RequerySuggested` 是进程级静态事件,`Delegate.Combine` 累积形成强引用链:静态 CommandManager → 委托链 → RelayCommand → Action → ViewModel - **检测**:dump 对比 / Finalizer 探针 / `GC.GetTotalMemory` 前后差值 - **方案 1(手动通知)**:`add { }` / `remove { }` 留空切断静态事件链,ViewModel 显式调 `RaiseCanExecuteChanged()` - **方案 2(WeakEventManager)**:自己写 `WeakEventManager` 子类,内部用 `WeakReference` 存储订阅者 - **方案 3(.NET 5+)**:`WeakReference<T>` + `ConditionalWeakTable` 自维护映射表 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 async/await 状态机 —— Day 4 --- ## 📊 Day 5 教学记录(详细) ### 今日主题 LINQ 延迟执行与表达式树(`IEnumerable<T>` vs `IQueryable<T>` / `ExpressionVisitor` / EF Core SQL 翻译) ### 教学重点 - **核心**:LINQ 是两层抽象的协同——`IEnumerable<T>` + 迭代器(CPU) vs `IQueryable<T>` + 表达式树(Provider 翻译) - **延迟执行本质**:链式调用只构造查询描述符(包装迭代器),`MoveNext()` 才执行;终结符(`ToList` / `Count` / `First`)触发立即执行 - **`Func` vs `Expression`**: - `Func` 是指向 IL 方法的函数指针(黑盒) - `Expression<Func>` 是数据结构(`Expression` 节点树),可遍历、可修改、可翻译 - **EF Core 翻译 5 步**:`IQueryable.Where` → `IQueryProvider` → `QueryCompiler` → `ExpressionVisitor` 深度遍历 → `RelationalSqlTranslatingExpressionVisitor` 生成 SQL + 参数化 - **常见误区**: - `Where` 不立即执行 - `Func` ↔ `Expression` 不可直接互转(`Func → Expression` 需 `Compile()`) - `AsEnumerable()` vs `AsQueryable()` 完全不同 - 多次枚举 `IEnumerable` 重跑管道、`IQueryable` 可能 N+1 查询 - **Q3 实战 WPF MVVM 动态过滤**: - **架构**:UI 规则 → `FilterBuilder<T>.Build` → `Expression<Func<T, bool>>` → EF Core → SQL - **4 层代码**:FilterRule 数据模型 + 强类型字段引用(`typeof(T).GetProperty` 反射校验)+ WPF ViewModel 集成 + 自定义 Visitor 扩展 - **5 种方案对比**:动态 LINQ / 手写 SQL 拼接 / DynamicExpresso / Lambda 拼接 / ExpressionVisitor 自建 - **最终选择**:ExpressionVisitor + 强类型字段引用(类型安全、可翻译、可扩展、可测试、性能最优) ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 反射与元数据 —— Day 6 --- ## 📊 Day 6 教学记录(详细) ### 今日主题 反射与元数据(CLR Metadata + Reflection API + 现代替代方案) ### 教学重点 - **核心**:元数据是数据(PE 文件里的二进制表),反射是 API(遍历表的访问器)—— 二者是"数据"和"访问器"的关系 - **CLR 元数据本质**:PE 文件 `.text` 段内的 40+ 张表(`TypeDef` / `MethodDef` / `FieldDef` / `Param` / `Property` / `Event` / `AssemblyRef` / `TypeRef`),表之间通过 **RID(Record ID)** 或 **token(4 字节 = 3 字节表索引 + 1 字节表标识)** 互相引用 - **IL 与元数据关系**:IL 中每条指令操作数(如 `callvirt 0A000003`)的 token 解析依赖元数据表 - **3 层反射性能**: - `MethodInfo.Invoke`:~1000-2000 ns(值类型装箱 + `object[]` 分配 + 反射虚调用) - `Delegate.CreateDelegate`:~15 ns(生成强类型委托,JIT 内联展开,**零装箱**) - `Expression.Compile`:~20 ns(生成 IL 动态方法,首次 ~50-100 μs) - **`[DynamicallyAccessedMembers]` 根本问题**:AOT 剪裁器**无法通过反射追踪可达性**——`Activator.CreateInstance(typeof(T))` 会让剪裁器把整个 `T` 删掉,运行时 `MissingMethodException`。注解本质是**把"运行时反射需求"前移到编译期**,Roslyn 写 PE 元数据 + IL Trimmer 读元数据保留成员 - **`MetadataLoadContext` 轻量扫描**:不把程序集加载到主 ALC,避免 `Assembly.LoadFrom` 强引用泄漏,适合插件系统/依赖分析工具 - **5 个常见误区**:`Invoke` 装箱值类型 / 不缓存 `MemberInfo` / `Assembly.LoadFrom` 强引用泄漏 / 反射破坏 AOT 剪裁 / `BindingFlags` 漏写 internal/static/protected - **关联知识**:依赖属性 `DependencyPropertyDescriptor` 元数据表 / `CommunityToolkit.Mvvm.SourceGenerators` 编译期生成 `INotifyPropertyChanged` / .NET 8 `PublishAot=true` + `[RequiresDynamicCode]` / `Expression.Compile` 内部也走反射+IL 生成 - **Q3 实战 WPF 通用属性映射器**: - **3 种方案对比**:纯反射版 ~50 万次/s(同名匹配硬编码) / 表达式树+规则 ~30-50 万次/s(`Expression.Property` 链构造嵌套属性) / 源生成 ~1000 万次/s(`[Mapper]` 特性 + Roslyn 生成强类型代码) - **嵌套属性处理**:split `"Address.Street"` → `["Address", "Street"]`,逐层构造 `Expression.Property` 链 - **行业现状**:AutoMapper 逐渐式微 / Mapster(表达式树) / Mapperly(源生成,性能比 AutoMapper 快 5-10 倍,零反射) - **5 个易错点**:每次 Map 都重新编译(必须 `ConcurrentDictionary` 缓存)/ 表达式树线程安全(`Compile()` 本身线程安全,编译结果共享)/ 嵌套属性 null 检查(生成代码插入 `Address != null ? ... : null`)/ AOT 剪裁掉 `typeof(T)` 需 `[DynamicallyAccessedMembers]` / 值类型装箱(`TSource` struct 时破坏性能,源生成可避免) ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 Span\<T\> / Memory\<T\> 与高性能编程 —— Day 7 --- ## 📊 Day 7 教学记录(详细) ### 今日主题 Span / Memory 与高性能编程(`ref struct` 零拷贝切片 / JIT 边界检查消除 / System.IO.Pipelines 5 层零分配架构) ### 教学重点 - **核心**:Span 是栈上值类型(`ref struct`),包装 `ref T _reference` + `int _length`,零拷贝切片、零分配 - **`ref struct` 4 大约束**:不能装箱 / 不能跨 await / 不能当类型参数 / 不能做字段(避免逃逸堆) - **JIT 边界检查消除**:`ref T` 让 JIT 通过 `_reference + index` 单指令访问,循环里所有 `Length` 检查被消除 → 向量化 + SIMD 自动 - **Memory\<T\>**:Span 的"非 ref struct"版本(可跨 await / 装箱 / 异步),但有 `Span` 属性间接寻址(~1 ns 开销) - **ArrayPool\<byte\>**:.NET Core 2.1+ 内置,池化复用数组,避免反复分配 Gen0 - **System.IO.Pipelines**:`Pipe` + `PipeReader` + `ReadOnlySequence<byte>`,5 层零分配(Socket → PipeWriter → 写入 → 解析 → UI) - **Q3 实战**:WPF 实时日志分析 5 层零分配架构(网络 ArrayPool + Pipeline 背压 + Span 零拷贝解析 + Channel 背压 + Dispatcher 节流) - **.NET 9 改进**:`allows ref struct` 泛型约束 + `ReadOnlySpan\<T\> Sequence<T>.AsSpan()` + 异步流零拷贝 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 CLR 内存模型与 GC 机制 —— Day 8 --- ## 📊 Day 8 教学记录(详细) ### 今日主题 CLR 内存模型与 GC 机制(分代假设 / Gen0-1-2 / LOH / POH / Workstation vs Server GC / Background GC) ### 教学重点 - **分代假设**:Weak Generational Hypothesis 实证观察(绝大多数对象生命周期短 + 老对象引用新对象极少)→ 分代 GC 设计的根本依据 - **5 个堆区对比表**: - Gen0(~256 KB,新生对象,频繁回收) - Gen1(Gen0 与 Gen2 之间的过渡,桥接) - Gen2(长命对象,偶尔回收,Full GC 才动) - LOH(Large Object Heap,≥ 85,000 字节阈值由来:避免压缩成本 + LOH 数组多为长命缓冲) - POH(Pinned Object Heap,.NET 5+,钉子对象独立堆,避免 Gen2 碎片) - **关键 API**:`GC.GetGeneration(obj)` / `GC.Collect(gen, mode)` / `GC.TryStartNoGCRegion` / `GCSettings.LatencyMode` / `GC.AllocateArray<T>(length, pinned: true)` - **GC 模式**:Workstation GC(桌面,单线程)vs Server GC(服务端,多线程并行,更高吞吐) - **Background GC**(.NET 4+):Gen2 回收时 Gen0/1 仍可分配,避免 Full GC 阻塞整个进程 - **6 大误区**:手动 `GC.Collect()` 通常有害 / 析构函数(Finalizer)导致对象升代 / LOH 不压缩导致碎片 / 钉子对象破坏 GC 效率 / 引用类型与 GC 关系 / `GC.TryStartNoGCRegion` 必须配套 `EndNoGCRegion` - **Q3 实战 WPF 实时行情 5 层零 GC 架构**: - UDP 网络层(1 万 tick/s × 1000 行情 = 1000 万/秒消息) - 解析层(struct 零分配 + `Span<T>` 切片) - 聚合层(环形缓冲区 + 池化对象) - 调度层(`Dispatcher.BeginInvoke` + 60 fps 节流) - UI 层(`Freezable` 冻结集合 + `OnRender` 重写 + `DrawingVisual`) - **诊断工具**:`dotnet-counters`(轻量采样)/ PerfView(深度事件追踪)/ `GC.GetTotalAllocatedBytes` / `GC.GetTotalMemory` ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 JIT 编译与 RyuJIT —— Day 9 --- ## 📊 Day 9 教学记录(详细) ### 今日主题 JIT 编译与 RyuJIT(双层编译模型 / RyuJIT vs 旧 x86 JIT / Tiered Compilation 3 层 / Dynamic PGO / ReadyToRun (R2R) / WPF 冷启动 5 层优化架构) ### 教学重点 - **双层编译模型**:C# → Roslyn → IL → RyuJIT → 机器码(vs 静态 AOT 编译) - **JIT 时机**:方法首次调用懒编译 + 进程内缓存 + 不跨进程共享(4 个原因:硬件/SIMD 假设 / OS 假设 / 安全 / 配置依赖) - **RyuJIT vs 旧 x86 JIT 6 维对比表**: - 位数(32 vs 64) - SIMD(无 vs AVX-512 完整) - 代码密度(松散 vs 紧凑 -30%) - 编译速度(慢 vs 快) - 代码质量(保守 vs 激进 SSA IR) - Tiered 支持(无 vs 有) - **3 个开发者可控 JIT 入口**: - `RuntimeHelpers.PrepareMethod(methodHandle)` 提前 JIT(启动后预热关键路径) - `[MethodImpl(MethodImplOptions.AggressiveOptimization)]` 方法级全局优化开关 - `DOTNET_JITTieredCompilation=0/1` 环境变量 + `ProfileOptimization` 多核并行 - **Tiered Compilation 3 层详解**: - **Tier 0 Quick JIT**(~30 次调用阈值):关闭内联/循环展开/向量化 → 启动期快速生成可执行代码 - **Tier 1 Full Optimization**:完整 SSA 优化 + 内联 + SIMD 向量化 - **Tier 2 PGO**(.NET 7+ Dynamic PGO 增强):运行时 Profile 采样 → 热路径激进内联 + 冷分支外提 + 虚调用去虚化(`Guarded Devirtualization`) - **ReadyToRun (R2R)**: - 发布时预编译 IL + 本地机器码到 PE 文件(支持多架构 PE) - 启动即加载机器码,等同 Tier 0 质量但**零编译延迟** - 与 NativeAOT 本质不同(保留 JIT 灵活性 + Runtime 反射能力) - **4 个诊断工具**: - `dotnet-counters`(`System.Runtime` 计数器,看 `jit-time`) - `dotnet-trace`(事件追踪 + 采样热点) - PerfView(Windows 深度,"JIT Stats" + "CPU Stacks" 视图) - `RuntimeHelpers.PrepareMethod` 启动后主动预热 - **WPF 冷启动典型分布**:JIT 占 30-50%(头号瓶颈)/ WPF BAML 解析 15-20% / XAML 元素实例化 10-15% / 首帧布局渲染 10-15% - **Q3 实战 5 层优化架构**: 1. **R2R**:启动期 JIT 时间下降 40-60%(包体积 +30-50%) 2. **Tiered 调优**:缩短启动期可见卡顿 3. **PrepareMethod 预热**:用户首次操作卡顿下降 80% 4. **ProfileOptimization 多核并行**:多核 CPU JIT 下降 50% 5. **渲染线程分离 + 延迟加载**:首屏可见时间下降 30-50% - **6 个绝对不能做的反模式**: - 启动期关闭 GC / NoGCRegion(OOM 风险) - 强制所有方法 Tier 1(启动期 CPU 密集) - 用 `MethodInfo.Invoke` 替代直接调用(反射 >> JIT 开销) - **WPF 不支持 NativeAOT**(截至 .NET 8)—— 反射 + BAML 依赖 Runtime - 使用 NGen.exe(已淘汰,被 R2R 取代) - 启动期 `Task.WhenAll` 加载所有服务(CPU 抢占拖慢首屏) - **关联知识网络**: - Day 1(值类型):RyuJIT 对 `struct` 展开/避免装箱优化 - Day 2(泛型):泛型 + JIT 共同决定 `List<int>` vs `List<string>` 性能 - Day 4(async):JIT 内联 async 状态机 `MoveNext`,async 性能关键 - Day 6(反射):`MethodInfo.Invoke` 走 JIT 内部动态方法编译路径 - Day 7(Span):JIT Tier 1+ 自动向量化 `Span<T>` 循环为 AVX 指令 - Day 8(GC):JIT 生成的代码 + GC 协同(栈分配、Boehm 标记) ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 异常处理机制(栈展开 / SEH)—— Day 10 --- ## 📊 Day 10 教学记录(详细) ### 今日主题 异常处理机制(CIL Exception Handling Table / Windows SEH / 栈展开 / 零开销哲学) ### 教学重点 - **核心**:异常处理是 CIL Exception Handling Table(编译期生成到 PE)+ Windows SEH(运行时派发)的双层抽象 - **CIL 异常表 4 字段**:`Flags`(Catch/Filter/Fault/Finally)/ `TryOffset`+`TryLength`(try 块 IL 范围)/ `HandlerOffset`+`HandlerLength`(handler IL 范围)/ `ClassToken` 或 `FilterOffset` - **关键 IL 指令**:`leave`(必须代替 `br`,保证 finally 触发)/ `rethrow`(opcode 0xFE 0x1A,保留原始栈)/ `endfinally` - **零开销哲学精确含义**: - ✅ 正常路径(无异常抛出):CPU 完全无额外开销(JIT 不写寄存器、不做检查) - ❌ 异常路径:~1-10 μs(创建 Exception 对象 Gen0 分配 + 栈展开 + 表查找 + 触发 finally) - ⚠️ finally 中大量代码:JIT 必须内联展开到所有 leave,代码膨胀拖累 I-Cache - **SEH vs Itanium ABI 7 维对比**: - 注册机制:静态表(编译期) vs 动态栈帧(throw 时构造) - 栈展开时机:**找到 handler 后才展开** vs throw 后立即展开 - 硬件异常支持:✅ 原生(访问违例/除零) vs ❌ 不支持 - 调试器集成:✅ First/Second Chance vs ❌ 不支持 - **`throw` vs `throw ex`**:IL 层分别是 `rethrow` 和 `ldloc.0; throw` —— 前者保留原始 StackTrace,后者重置到 throw 行 - **First Chance vs Second Chance Exception**: - First Chance:异常**首次抛出时**触发,CLR + 调试器订阅者接收(VS 的"异常设置"窗口) - Second Chance:异常未被任何 handler 捕获、即将终止进程时触发(dump 抓取时机) - **6 大误区**: 1. `throw ex` vs `throw` —— 栈重置陷阱 2. catch 后吃掉异常 —— No-op Catch 调试地狱 3. 用异常做正常控制流 —— 失败场景 `int.Parse + try/catch` 比 `int.TryParse` 慢 50-100 倍 4. finally 不一定执行 —— StackOverflowException(.NET Core 2+ 完全不可 catch)/ FailFast / kill -9 / OOM 提前崩溃 / AppDomain 卸载 5. catch 顺序错误 —— 多态陷阱(`catch (Exception)` 永远先匹配) 6. `AggregateException` 只属于 TPL —— `Task.WhenAll().Wait()` 包装,业务层应 `Flatten()` 处理 - **零开销断言 4 方案对比**: - `Debug.Assert` —— Release **完全移除**(条件编译) - `Contract.Requires<T>` —— Release **完全移除**(Code Contracts,仅 .NET Framework,已废弃) - 自定义 `Guard.AgainstNull(x)` —— Release **保留**(强制) - .NET 7+ `Throw.IfNull(x)` —— Release **保留**(推荐) - **失败处理 4 方案对比**: - `try/catch`(异常流):性能(成功零开销/失败 1-10 μs)、错误信息丰富、生态友好,但强制处理弱 - `Result<T>`(Rust 风格返回值):性能(成功 1ns/失败 1ns)、强制处理,但生态弱 - `OneOf<T1,T2>` 库:强制处理 + 多态错误,但引入第三方 + 装箱风险 - `Try*` 模式(TryParse):零开销,但 bool 强制弱 + 错误信息弱 - **Q3 实战 WPF 金融交易终端 5 层异常架构**: - **网络层**:捕获 SocketException/TimeoutException,转换为 TransientOrderException(业务异常);其他异常不捕获,上抛 - **业务层**:catch TransientOrderException → Retry / catch OrderValidationException → 拒绝订单 / catch Exception → 告警 + 兜底 - **调度层**:Dispatcher 异常隔离,UI 更新失败不影响业务正确性 - **聚合层**:异常采样上报(避免高频异常拖垮遥测管道) - **遥测层**:Channel 背压 + 后台线程消费(异步上报,不阻塞业务路径) - **实战策略**: - 业务层用 `try/catch`(异常即业务事件) - 核心热路径用 `TryParse` 风格(避免异常拖慢 P99) - 跨层边界用 `Result<T>`(强制调用方处理) - 多态错误用 `OneOf<T1,T2>`(业务有多种合理失败) - **5 个反模式**:catch 代替 if / 无脑包装 / 空 catch / finally 中抛异常 / 异步中吞异常 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 线程池与 TaskScheduler —— Day 11 --- --- ## 📊 Day 11 教学记录(详细) ### 今日主题 CLR 线程池与 TaskScheduler(`ThreadPool` / Work-Stealing / Hill Climbing / Starvation Avoidance / `TaskScheduler` / `LongRunning` / WPF UI 调度) ### 教学重点 - **核心**:CLR ThreadPool 是进程级共享线程池,由 4 大设计哲学驱动——线程复用(~100μs 避免 + 1MB 栈 + 句柄开销)/ 自适应并发(Hill Climbing → Starvation Avoidance)/ 工作窃取(本地 LIFO + 偷取 FIFO 缓存友好)/ 注入控制 - **ThreadPool 4 大设计哲学**: 1. **线程复用**:避免反复创建/销毁线程(~100μs + 1MB 栈 + 内核对象 + 句柄表项 + GC 析构) 2. **自适应并发**:min=CPU 核心数, max=32,767,根据负载动态注入/回收 3. **工作窃取(Work-Stealing)**:每个 worker thread 优先消费本地队列,空闲时从其他线程的本地队列尾部窃取 4. **注入控制**:早期 Hill Climbing 爬山算法(采样/调步/震荡缺陷)→ .NET Core 3+ Starvation Avoidance 主动监控饥饿时间 ~500ms - **Work-Stealing 算法详解**: - **本地队列**:环形数组(MPSC,Multi-Producer Single-Consumer),默认长度 16 - **本地 LIFO**:自己刚 push 的热数据,缓存命中 - **偷取 FIFO**:对方的旧数据,**避免对方已预热的缓存行被偷走** - **Hill Climbing 伪代码**:每 1-10 秒采样吞吐 → 升降线程数 → 缺陷(响应慢/震荡/长尾不友好) - **Starvation Avoidance 伪代码**:worker 等待全局队列 > 500ms → 立即注入新线程 → 优势(突发友好/无震荡/长尾友好) - **`TaskCreationOptions.LongRunning` 底层**:CLR 内部 `new Thread(IsBackground=true).Start(action)`,独立线程不入池,释放池容量给短任务 - **长任务 3 大危害**:worker 线程被占满饥饿风险 / 线程注入失控上下文切换成本反升 / GC 根膨胀扫描成本高 - **`TaskScheduler` 解耦作用**:把"线程池"从"线程执行"中抽离——同一 `Task.Run` 可调度到 ThreadPool / DispatcherSynchronizationContext / 自定义调度器 / 调试可视化 - **WPF UI 调度核心**: - UI 线程默认有 `DispatcherSynchronizationContext`,await 自动捕获 - 续作自动回到 UI 线程(无需显式 `Dispatcher.Invoke`) - 库代码一律 `.ConfigureAwait(false)` 避免上下文捕获开销 - **真异步 I/O 关键**:HttpClient.SendAsync / Stream.ReadAsync / Socket.ReceiveAsync 走 IOCP(I/O 完成端口),**不占 worker thread**——这是线程池饥饿的真正解药 - **6 大误区**: 1. `Task.Run(...).Result/.Wait()` 在 UI 线程死锁 2. 假异步包装(`Task.Run(() => File.ReadAllText)`)浪费线程池 3. 大量并发 I/O 调大 `SetMinThreads` 错误(I/O 走 IOCP) 4. `LongRunning` 当作优化开关(滥用 = 退化到 new Thread) 5. `Task.Delay(1000).Wait()` 占线程池(应该 await 或 Thread.Sleep) 6. `Task.Run` 内捕获 SynchronizationContext(依赖上下文易死锁) - **Q3 实战 WPF 实时看板 5 层架构**: - **UI 层**:DispatcherSynchronizationContext 隐式捕获 + IProgress<T> 自动回 UI + CancellationTokenSource 触发 - **调度层**:Task.WhenAll + 独立 Task 包装(5 个全并发,扩展 50+ 用 SemaphoreSlim 限流) - **业务层**:每个 Task 独立 try/catch(异常隔离) + HttpClient.SendAsync 真异步 - **进度聚合层**:Channel<ProgressReport> 背压解耦 + Dispatcher 节流到 60fps + Progress<T> 内部节流 - **遥测层**:Channel 消费失败上报 + 后台线程异步上报不阻塞业务 - **性能对比表**: | 方案 | 5 个服务总耗时 | UI 卡顿 | 线程池占用 | |------|---------------|---------|-----------| | 同步顺序调用 | 1000 ms | 1000 ms 完全卡死 | 1 worker | | `Task.Run × 5 + .Wait()` | 200-300 ms | **死锁 ❌** | 5 worker + UI 卡 | | `Task.WhenAll + ConfigureAwait` | **200-300 ms** | **60 fps 流畅** | **0 worker(IOCP)** | | `SemaphoreSlim(3)` 限流 | 300-400 ms | 60 fps 流畅 | 0 worker(IOCP) | - **关键洞察**:HttpClient.SendAsync 走 IOCP,**即使 500 个并发请求也是零 worker 占用**。线程池饥饿的真正杀手是 `Task.Run` + 同步 I/O 滥用 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 锁机制(Monitor / SemaphoreSlim / AsyncLock)—— Day 12 --- --- ## 📊 Day 12 教学记录(详细) ### 今日主题 锁机制深度剖析(`lock` / `Monitor` / `SemaphoreSlim` / `AsyncLock` / `ReaderWriterLockSlim` / 内存模型语义) ### 教学重点 - **核心**:锁是同步原语——CPU 原子指令(`Interlocked`)→ 用户态自旋(`SpinLock`)→ 混合锁(`Monitor`/`SemaphoreSlim`)→ 内核态锁(`Mutex`/`Semaphore`)→ 跨进程内核(命名 Mutex/Semaphore)—— 性能/通用性递进 - **`lock` 是 `Monitor` 语法糖**:编译器生成 `try { Monitor.Enter(obj, ref lockTaken); ... } finally { if (lockTaken) Exit(); }`,`lockTaken` 是 .NET 4.0 引入的异常安全参数 - **7 种同步机制 6 维对比**:性能(ns/μs/ms)/ 跨进程(❌/✅)/ 异步(❌/✅)/ 底层机制(CPU指令/对象头/内核对象)/ 持锁线程要求 / 典型场景 - **`lock` 不能跨 await 的 3 大原因**: 1. 线程亲和性(`Monitor.Exit` 必须由持锁线程调用,await 后续可能换线程) 2. 永久死锁风险(await 期间锁被另一线程获取,原线程回来等待自己持的锁) 3. finally 不可靠(早期 .NET Framework 的 `ThreadAbortException` 会中断 finally) - **CLR 对象头 Sync Block Index**: - 64 位对象头:前 4 字节 Sync Block Index,后 8 字节 MethodTable Ptr - 索引 = 0 未锁定 / -1 Thin Lock(线程 ID + 递归计数存对象头)/ > 0 Fat Lock(指向 Sync Table 条目) - **Thin → Fat 升级**:另一线程进入时,CLR 分配 Sync Block Entry,对象头指向 Entry - **内存屏障语义**: - `Monitor.Enter` = **acquire fence**(之后读写不能前移) - `Monitor.Exit` = **release fence**(之前读写不能后移) - `Monitor.Enter + Exit` = **full fence** - `volatile` 字段 = 字段读写自带 acquire/release fence - `Volatile.Read/Write` = 显式 acquire/release - `Interlocked.MemoryBarrier()` = full fence - **.NET 内存模型**:x86/x64 强模型(天然有序)/ ARM64 弱模型(需要显式 acquire/release)—— 业务代码应**始终**用 `volatile`/`lock`/`Interlocked`,不依赖特定架构 - **`ReaderWriterLockSlim` 递归陷阱**: - 默认 `LockRecursionPolicy.NoRecursion`,同线程递归进入抛 `LockRecursionException` - `SupportsRecursion` 启用后:读读递归 ✅ / 读写升级 ✅ / 写读降级 ⚠️ / 写写递归 ✅ - 升级规则:`EnterReadLock` → `EnterWriteLock` 直接抛 `LockUpgradeException`(除非 SupportsRecursion) - **自建 `AsyncLock` 生产模板**: - 内部 `SemaphoreSlim(1,1)` + 预创建 `_releaser` Task(避免每次 lock 分配) - 快速路径(未争用)直接返回已完成的 task - 慢速路径用 `ContinueWith` 在 ThreadPool 上创建 releaser - 现成方案:`Nito.AsyncEx.AsyncLock` - **`Monitor.Wait` 语义**:在 lock 内调用会**原子地释放锁 + 阻塞当前线程 + 加入等待队列 + 等待 `Pulse/PulseAll`**,被唤醒后**重新获取锁**继续执行 - **Q3 实战 WPF 实时行情订阅 5 层架构**: - **数据源层**:4 个行情源(CTP/LTS/Binance/Simulated)→ 各自推送线程 → `Channel<Tick>` bounded=10000(背压) - **聚合层**:单一后台线程消费 Channel → `AsyncLock` 写 OrderBook - **业务层**:`AsyncLock` 保护 `Dictionary<string, Order>` + 重建 `ImmutableArray<Order>` 快照 - **持久化层**:`PeriodicTimer` 每 100ms 读快照 → 异步写 SQLite(不持业务锁) - **UI 层**:Dispatcher 60fps + `CompositionTarget.Rendering` 帧对齐 + 差异检测 - **5 大陷阱**: - T1:UI 线程死锁(`_book.GetSnapshot()` 内 `.Result/.Wait()`) - T2:Channel 背压失效(`Unbounded` + 多生产线程 → OOM) - T3:持久化层锁订单簿(await IO 时间过长) - T4:聚合线程单点故障(异常被吞) - T5:Dispose 时死锁(`WaitAsync` 没传 `CancellationToken`) - **4 种方案对比**: - A:`Monitor` 全程 —— 最简单但跨 await 不支持 ❌ - B:`AsyncLock` + `SemaphoreSlim` —— MVP 阶段推荐 ✅ - C:`ReaderWriterLockSlim` + `ImmutableArray` —— 读多写少 >10:1 - D:`Channel<T>` + 不可变 + Lock-Free —— **生产级推荐**(金融终端工业标准) - **8 个常见误区**: - `lock(this)/lock(typeof)/lock(string字面量)` —— 灾难性 - lock 内 await —— CS1996 警告 + `SynchronizationLockException` - `volatile` 不能替代锁 —— `x++` 是 read-modify-write 非原子 - `SemaphoreSlim` 必须传 CancellationToken + try/finally Release - `ReaderWriterLockSlim` 升级规则(读→写直接抛异常) - finally 中 await 后释放锁(拉长持锁时间) - 用 lock 限流(应该用 `SemaphoreSlim(maxCount)`) - `SemaphoreSlim` 不 Dispose 会泄漏资源 - **关联知识网络**: - Day 4(async/await):await 跨线程 → `lock` 不能跨 await → 必须用 `SemaphoreSlim` - Day 8(GC):锁对象越小越好(Gen0 快速回收) - Day 10(异常):try/finally 释放锁是基础设施 - Day 11(ThreadPool):`SemaphoreSlim.WaitAsync` 内部用 TCS 复用线程池 - Day 7(Span):`lock` 不能保护 `ref struct`(不能装箱为 `object`) ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 并行编程(Parallel / PLINQ / Channel<T> / TPL Dataflow)—— Day 13 --- ## 📊 Day 13 教学记录(详细) ### 今日主题 .NET 并行编程全景(`Parallel.For/ForEach` / `PLINQ` / `Channel<T>` / `TPL Dataflow` / `Task.WhenAll` 5 大原语对比 / 分区算法 / 背压协议 / WPF 5000 张 4K 图像 5 层流水线实战) ### 教学重点 - **5 大并行原语层级表**(抽象层次/并行模型/调度开销/适用场景): - `Parallel.For/ForEach` —— 数据并行(DOP)/ ~10-100 ns/项 / CPU 密集迭代独立 - `PLINQ` —— 数据并行 + 流水线 / ~100-500 ns/项 / 查询式可分区 - `Channel<T>` —— 生产者-消费者 + 背压 / ~50-200 ns/项 / 异步流水线解耦 - `TPL Dataflow` —— 块网络(Mesh)/ ~200-500 ns/项 / 多阶段流水线扇出扇入 - `Task.WhenAll` —— 显式并发 / 自定义 / 已知 N 个独立 Task - **3 个并行设计目标**:自动分区 + 自动负载均衡 + 背压控制 - **Amdahl 定律警告**:串行部分 S 决定最大加速比 `1/S` - **4 个最小代码示例**: - `Parallel.For(0, 1000, i => ...)` + `ConcurrentBag<T>` - `AsParallel().Where(...).OrderByDescending(...).Take(100).ToList()` - `Channel.CreateBounded<Tick>(10000, FullMode=Wait)` - Dataflow `TransformBlock → TransformBlock → ActionBlock` 链接 - **10 个反模式表**:Parallel 处理 I/O / PLINQ 处理小数据集 / BlockingCollection / 共享状态无锁 / 不处理 AggregateException / Parallel.For 默认有序 / Channel Unbounded / Dataflow 不设 BoundedCapacity / 并行 lambda 内 await / 并行度 = int.MaxValue - **`Parallel.For` 分区算法 3 种**: - **静态分区**(默认):等分成 K=ProcessorCount 段 → 缓存友好 / 负载不均 → 均匀任务 - **动态分区**:`Partitioner.GetDynamicPartitions()` → 负载均衡 / 缓存差 / 同步开销 → 不均任务 - **Chunk 分区**(推荐折中):每段 N=ProcessorCount 项 → 平衡两者 - 实测数据:10000 项订单 1-100ms 随机,静态 1.2s / 动态 700ms / Chunk 750ms - **数据并行 vs 流水线并行**: - **数据并行**(`Parallel.ForEach`):N 个独立项 → K 个 worker 同时处理(空间换时间) - **流水线并行**(PLINQ 内部):各阶段独立并行(`Where → Select → OrderBy`),项 1 完成 Where 立即进入 Select(**Stage-Level Parallelism**,即使少量数据也能提速) - ⚠️ **`OrderBy` / `Aggregate` / `Take` 强制串行**(需全局顺序) - **PLINQ = 数据并行 + 流水线并行**(二维并行杀手锏) - **Channel 背压 5 策略对比**: - `Wait`(生产者 await 阻塞)—— **金融首选**(订单零丢失) - `DropOldest`(丢队首)—— 日志流 - `DropNew`(丢队尾)—— 监控采样 - `DropWrite`(丢当前)—— 非关键事件 - `Throw`(抛 ChannelClosedException)—— 测试 - **Q3 实战 WPF 5000 张 4K 图像 5 层流水线**: - **架构**:Loader(4w, I/O) → Resizer(8w, CPU) → Sharpener(8w, CPU) → Encoder(4w, CPU) → Saver(2w, I/O) - **worker 数差异原理**:CPU 紧层满载 `ProcessorCount` / I/O 层 4-2(磁盘/网络瓶颈) - **生产代码 5 层 Channel + Dispose + CancellationToken 透传 + Writer.Complete 优雅关闭** - **5 种方案对比表**:Parallel.ForEach 全程 ❌(OOM + UI 死锁 / ~45s)/ PLINQ ❌(同 / ~50s)/ Dataflow ✅(~30s)/ Channel+Task ✅(~28s)/ Channels+Dataflow ✅✅(~25s,**生产首选**) - **内存控制 4 策略**:Bounded Channel + 及时 Dispose + Span+ArrayPool + 流式处理 - **3 层取消防护**:CancellationToken 透传 + Writer.Complete + OperationCanceledException 清理 - **性能预期**:整体 ~10 min(流水线瓶颈 = 最慢层 Saver 4min)vs 串行 ~80 min = **8x 加速** - **5 大陷阱**:UI 线程死锁 / 内存爆炸 / BoundedCapacity 缺失 / Bitmap 不 Dispose / 取消未清理 - **3 道面试题答案要点**: - **Q1**:5 大原语 5 维对比表 + 区分并行模型(数据并行/流水线并行)+ UI 线程禁用 `Parallel.For` - **Q2**:静态 vs 动态 vs Chunk 分区 trade-off + 数据并行 vs 流水线并行本质区别 + Channel `Wait` 是金融唯一选择 - **Q3**:5 层流水线架构 + 完整生产代码 + 5 方案对比 + 内存控制 + 性能预期 + 5 陷阱 - **10 倍扩展思考**:多机分布式 / 内存映射文件 / GPU 加速(DirectX/Vulkan 4K 缩放 20-50x)/ 流水线启动延迟 vs 短任务陷阱 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 WPF 架构总览(PresentationFramework / PresentationCore / WindowsBase + XAML 编译原理 BAML + InitializeComponent 反射 vs 编译期生成 + WPF 依赖 Runtime 不能 NativeAOT)—— Day 14(**Phase 3 开篇**) --- ## 📊 Day 15 教学记录(详细) ### 今日主题 WPF 依赖属性(DependencyProperty)核心(CLR 9 大局限 / 11 级优先级 / 3 大回调 / `SetValue` vs `SetCurrentValue` / `PasswordStrengthIndicator` 实战) ### 教学重点 - **核心抽象**:DP = `static readonly DependencyProperty` 全局单例标识符 + `DependencyObject` 内部 `EffectiveValueEntry[]` 懒分配动态存储。**DP 不是字段,是值解析框架** —— 每次 `GetValue` 都走 11 级优先级查找 + 表达式求值 - **CLR 9 大局限**:值继承 / 数据绑定 / 动画 / 样式 Setter / 模板 TemplatedParent / 强制值 / 验证回调 / 变更通知 / 资源引用 —— 9 项全部 ❌ - **WPF 不抛弃 CLR 包装 3+1 原因**: 1. 编译时类型检查(`Value="abc"` 编译期报错 vs DP 运行时抛 `XamlParseException`) 2. IDE IntelliSense + XAML 设计器智能提示 3. 重构友好(rename 同步所有引用) 4. 隐藏原因:CLR 包装是 XAML 解析器发现属性的入口(XAML 只认 `public` CLR 属性 + 匹配 DP 静态字段) - **`EffectiveValueEntry` 懒分配**:1000 个 Button 只设 10 个 Width → 只有 10 个槽位被分配。**未设置的 DP 零内存占用** - **DP 全局单例**:`Button.WidthProperty` 整个 AppDomain 唯一,所有 Button 共享标识符,只是各自 `EffectiveValueEntry` 不同 - **完整 `NumericUpDown` 生产代码**: - `ValueProperty` 注册(`AffectsRender` + `BindsTwoWayByDefault`) - 3 大回调完整签名(`ValidateValue` / `CoerceValue` / `OnValueChanged`) - `Min`/`Max` 变更触发 `CoerceValue(ValueProperty)` 联动 - `DefaultStyleKeyProperty.OverrideMetadata` 指定默认样式 - **11 级值解析优先级**(核心铁律): - 1️⃣ Coerce 最高(任何来源都被 Coerce) - 2️⃣ 动画(`Storyboard` 覆盖本地值) - 3️⃣ 本地值(`SetValue` 显式设置) - 4️⃣ TemplatedParent(模板内属性) - 5️⃣ 样式触发器(Trigger 覆盖 Setter) - 6️⃣ 样式 Setter(覆盖主题) - 7️⃣ 主题样式触发器 - 8️⃣ 主题样式 Setter - 9️⃣ 继承值(`Inherits=true` 沿视觉树向下传) - 🔟 默认值最低(兜底) - **3 大回调执行顺序**:`ValidateValue`(门卫,非法抛 `ArgumentException`)→ `CoerceValue`(矫正师,调整到合法范围)→ `PropertyChanged`(广播员,通知值已变更) - **`SetValue` 完整事件链伪代码**:Validate → Coerce → 短路检查(同值不重复触发)→ 写 `EffectiveValueEntry` → PropertyChangedCallback → `OnPropertyChanged(dp.Name)` → `BindingExpression.UpdateSource()` 双向绑定反向通知 - **深度解析**:DP 不是字段是**表达式** —— `EffectiveValueEntry` 含 `Expression` 字段表示未求值的绑定/动态资源,WPF 在 `Expression` 层拦截实现 Binding 改写 DP(**不需要 `SetValue`**) - **`SetValue` vs `SetCurrentValue` 5 维对比**: - 写入层级:本地值 vs 当前值 - 打断动画:✅ vs ❌ - 打断数据绑定:✅ vs ❌ - 打断样式触发器:✅ vs ❌ - 触发 PropertyChanged + Coerce:都 ✅ - **关键洞察**:`SetValue` = 完全掌控(清空所有其他源)/ `SetCurrentValue` = 柔和更新(只改当前展示值) - **`RegisterReadOnly` + `DependencyPropertyKey` 模式**: - `DependencyPropertyKey` 持有 `SetValue` 权限(私有静态字段) - 公开 `DependencyProperty` 给外部只读 `GetValue` - 避免外部绕过 Coerce 直接 `SetValue` - **`ClearValue` vs `SetValue(defaultValue)` 铁律**: - `ClearValue` = 删除本地值(回到继承/默认) - `SetValue(defaultValue)` = **显式本地值**(优先级 3️⃣ 最高之一) - 二者完全不同! - **`OverrideMetadata` vs `AddOwner` 区别**: - `OverrideMetadata` = 改变继承类元数据(同 DP,**同 Owner**) - `AddOwner` = 把别人注册的 DP 借到自己类上(**多 Owner**) - **`RegisterAttached` 附加属性**: - 是"寄生"在其他类上的 DP(如 `Grid.Row`) - **不能写 CLR 包装**(用静态 `GetXxx`/`SetXxx` 方法访问) - **Q3 `PasswordStrengthIndicator` 完整生产代码**: - `PasswordProperty`(`AffectsRender` + `BindsTwoWayByDefault`) - `StrengthProperty`(`RegisterReadOnly` + `DependencyPropertyKey` + Coerce 强制空密码为 Weak) - `ForegroundProperty`(`AddOwner` 到 `Control.ForegroundProperty` + `Inherits`) - `OnPasswordChanged` 用 `SetCurrentValue(StrengthPropertyKey, ...)`(**绝不**用 `SetValue`,否则 Coerce 失效) - `CoerceStrength` 读 `ctrl.Password` 当前值判断(不是 `baseValue`) - 强度算法(长度 < 6 Weak / 1 项 Medium / 2+ 项 Strong) - **5+3 大生产级陷阱**: - T1 双向绑定死循环 → 单向 `Mode=OneWay` - T2 Coerce 无限递归 → 直接 `return` 不 `SetValue` - T3 `SetValue` 清空 Coerce → 用 `SetCurrentValue` - T4 Strength 未只读保护 → `RegisterReadOnly` + 私有 `Key` - T5 Foreground 缺 `Inherits` → 标记 + `AddOwner` - T6(隐藏)异步计算防抖 → `CancellationTokenSource` - T7(隐藏)`OnPasswordChanged` 内不抛异常 → 避免打断 WPF 状态机 - T8(隐藏)`Foreground` 用 `AddOwner` 而非 `new PropertyMetadata` → 避免双 Foreground DP - **关联知识网络**: - **Day 14 WPF 架构** → DP 注册走 `static readonly` 反射 → WPF 不能 NativeAOT 第二大根因 - **Day 11 ThreadPool** → `DispatcherSynchronizationContext` 让 `OnPasswordChanged` 自动在 UI 线程执行 - **Day 6 反射** → `DependencyPropertyDescriptor` 是反射访问 DP 的元数据描述器 - **Day 8 GC** → `EffectiveValueEntry[]` 按需分配,未设置的 DP 零内存占用 - **未讲 Day 17 路由事件** → `RoutedEventArgs.OriginalSource` 本质是 DP - **未讲 Phase 4 数据绑定** → `Binding` 目标必须是 DP - **未讲 Phase 4 样式/动画** → `<Setter>` 和 `Storyboard` 只能操作 DP ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 路由事件(Routed Event)—— Day 16(3 种路由策略 Bubble/Tunnel/Direct / `RoutedEventArgs.Source` vs `OriginalSource` / `EventManager.RegisterRoutedEvent` + `RaiseEvent` / 附加事件 / 5 大使用陷阱) --- ## 📊 Day 16 教学记录(详细) ### 今日主题 WPF 路由事件(Routed Event)核心(CLR 事件 vs 路由事件本质区别 / 3 种路由策略 / Source vs OriginalSource / 视觉树 vs 逻辑树 / Handled + handledEventsToo / EditableUserCard 双模式实战) ### 教学重点 - **核心抽象**:路由事件 = 能在元素树(视觉树)中按特定方向传播的事件 = `static readonly RoutedEvent` 全局单例 + `RoutedEventArgs` 携带 Source/OriginalSource/Handled + `EventManager.RegisterRoutedEvent` 注册机制 - **3 种路由策略核心铁律**: - **Bubble**(冒泡):Source → 根,**无前缀**(Click / MouseLeftButtonDown)——最常用 - **Tunnel**(隧道):根 → Source,**Preview 前缀**(PreviewMouseDown / PreviewKeyDown)——父容器提前拦截 - **Direct**(直接):不传播(Loaded / Unloaded)——类似 WinForms - **完整传播顺序图**(Button 在 StackPanel → Grid → Window 中被点击): ``` Window.PreviewMouseDown ← Tunnel(根向下) └ Grid.PreviewMouseDown └ StackPanel.PreviewMouseDown └ Button.PreviewMouseDown ← Tunnel 终点 └ Button.MouseLeftButtonDown ← Direct 触发(Source) └ StackPanel.MouseLeftButtonDown ← Bubble(Source 向上) └ Grid.MouseLeftButtonDown └ Window.MouseLeftButtonDown ``` - **4 大铁律**:Tunnel 总在 Bubble 之前 / Button 自己既是 Tunnel 终点又是 Bubble 起点 / Tunnel 数量=树深度 / Bubble 数量=树深度+1 - **3 步骤生产代码**:`RegisterRoutedEvent`(名字+策略+handler类型+owner)→ CLR 包装(add/remove 用 AddHandler/RemoveHandler)→ RaiseEvent(必须先 base.OnXxx(e)) - **Source vs OriginalSource 对比表**: - `Source`:逻辑源,谁 RaiseEvent 谁就是 Source(触发代码决定) - `OriginalSource`:物理源,InputManager 命中测试的实际元素(命中测试决定) - 场景 1:Button 内 TextBlock 模板 → Source=Button / OriginalSource=TextBlock(最常见) - 场景 2:自定义 ControlTemplate 内 Border → OriginalSource=Border(模板嵌套) - 场景 3:OnApplyTemplate 动态元素 → Source 任意/OriginalSource 命中测试 - **视觉树 vs 逻辑树双树架构**: - **逻辑树**(Logical Tree):XAML 声明层级,用于 DataContext 继承 / 资源查找 / ItemsControl 容器识别 - **视觉树**(Visual Tree):实际渲染的所有视觉元素(含模板内部 / Adorner 装饰器) - **路由事件沿视觉树传播**,**数据绑定沿逻辑树解析**——这是两个完全不同的遍历路径 - **Handled 机制 + handledEventsToo 父容器精确拦截**: - `e.Handled = true`:默认停止冒泡 - `AddHandler(routedEvent, handler, handledEventsToo: true)`:外层仍能接收已处理事件 - XAML `+=` / C# `event +=` **无法传 handledEventsToo**,必须用 `AddHandler` - **附加事件(Attached Event)vs 路由事件**: - 附加事件:"寄生"在其他类上的路由事件(如 `Mouse.MouseDown` 给任意 UIElement) - XAML 写 `MouseHelper.HoverEnter="..."` 实际是调用静态 `AddHoverEnterHandler`,C# 直接 `+=` 是错的 - **Q3 实战 EditableUserCard 双模式 ListBox**: - **❌ Tunnel 方案 3 大致命缺陷**:Button 视觉效果丢失 / 无法阻止 Bubble 触发删除 / 点删除同时显示详情 - **✅ Bubble+Handled+handledEventsToo 完整方案**: ```csharp // Button 内 e.Handled = true 阻止冒泡 private void DeleteButton_Click(object sender, RoutedEventArgs e) { e.Handled = true; // 关键 DeleteUser(); } // ListBox 用 AddHandler + handledEventsToo 仍接收 listBox.AddHandler(Mouse.MouseDownEvent, new MouseButtonEventHandler(OnRowClick), handledEventsToo: true); // FindAncestor<T> 视觉树遍历判断 OriginalSource 是否落在 Button ``` - **4 个关键技术点**:Handled 在 Button 设 / AddHandler 而非 += / OriginalSource 而非 Source / FindAncestor 而非 e.Source - **5 种方案对比表**:Bubble+Handled ✅ 生产级推荐 / Tunnel 全局快捷键 / 附加事件第三方控件 / Adorner 拖放选择 / RoutedCommand 复杂框架 - **5 大生产陷阱**:漏 e.Handled=true / 用 += 而非 AddHandler / e.Source is Button 永远 false / FindAncestor 用逻辑树 / 异步 await 后改 Handled 不可变 - **关联知识网络**: - **Day 15 DP** → `RoutedEventArgs.Source` 由触发代码决定,事件传播过程中 WPF 用 DP 系统解析元素位置 - **Day 14 WPF 架构** → `EventManager.RegisterRoutedEvent` 走 `static readonly` 反射注册,是 WPF 不能 NativeAOT 第三大根因 - **Day 11 ThreadPool** → 路由事件默认同步触发,`BeginInvoke` 可异步派发到 UI 线程 - **未讲 Phase 4 命令系统** → `RoutedCommand` 完全依赖路由事件机制(Execute/CanExecute 沿视觉树冒泡) - **未讲 Phase 4 数据绑定** → `Binding.Source` 沿逻辑树解析 DataContext,与路由事件视觉树路径完全不同 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 视觉树与逻辑树(Visual Tree vs Logical Tree / `VisualTreeHelper` / `LogicalTreeHelper` / 模板展开与视觉子树 / `FindAncestor` / `VisualBrush` / 装饰器 Adorner / 命中测试 HitTest)—— Day 17 --- ## 📊 Day 14 教学记录(详细) ### 今日主题 WPF 架构总览(7 层程序集分层 / `WindowsBase`↔`PresentationCore`↔`PresentationFramework` 依赖关系 / XAML 编译原理 BAML / `InitializeComponent` 反射加载机制 / Milcore C++ COM 跨进程 / WPF 不能 NativeAOT 的 5 大根因) ### 教学重点 - **核心洞察**:WPF 不是一个类库,是一个**多程序集分层架构**。理解 7 层架构 + BAML 编译 + InitializeComponent 反射 + Milcore 跨进程,你对 WPF 的认知就超过了 80% 的 WPF 开发者 - **7 层程序集全景**: - **Milcore** (milcore.dll + WindowsCodecs.dll) —— C++ COM 跨进程 + Direct3D 渲染 - **WindowsBase** —— 窗口系统无关:`DispatcherObject` / `DependencyObject` / `Freezable` / `DependencyProperty` / `WeakEventManager` - **PresentationCore** —— `Visual` / `UIElement` / `FrameworkElement` / `Brush` / `Geometry` / 命中测试 / 布局引擎 - **System.Xaml** —— 通用 XAML 解析器(独立于 WPF):`XamlReader` / `XamlWriter` / `MarkupExtension` - **PresentationFramework** —— WPF 顶层:`Application` / `Window` / `Page` / 全部内置控件 / `Style` / `ControlTemplate` / `DataTemplate` - **System.Xml + System.Markup** —— XML 解析基础设施 - **应用层** —— .exe + .xaml + App.xaml.cs + .g.cs(设计器生成) - **依赖方向图**:`PresentationFramework → PresentationCore + System.Xaml → WindowsBase`,**反向引用不存在** - **设计哲学**:关注点分离 + 可测试性 + 渐进式复杂度。WindowsBase 与 PresentationCore 分离让底层类型不依赖具体窗口模型;PresentationCore 与 PresentationFramework 分离让 `UIElement` / `FrameworkElement` 可独立测试;System.Xaml 独立让 UWP/WinUI/WCF/WF 共享同一套解析器 - **XAML→BAML 编译 4 步**: 1. XamlBuildTask 语法解析 2. **设计时实例化**(构建期发现错误) 3. XamlWriter 序列化为 BAML 字节流 4. gzip 压缩嵌入 .exe 的 `.mresource` 区 - **`InitializeComponent` 运行时 5 步**: 1. 构造 BAML 资源 URI(`/MyApp;component/mainwindow.baml`) 2. `Application.LoadComponent(this, uri)` 调用 3. `Assembly.GetManifestResourceStream()` 打开 BAML 流 4. `Baml2006Reader` 读取字节流 5. `WpfXamlLoader.Load()` 反射调用 setter + 事件处理器(`GetEvent` + `GetMethod` + `Delegate.CreateDelegate`) - **XAML 文本 vs BAML 二进制 6 维对比**:解析速度(BAML 5-10x 快)/ 内存占用 / 类型查找(TypeId 预解析 O(1))/ 属性设置 / 启动期 CPU / 调试体验(BAML 错误指向偏移而非行) - **WPF 不能 NativeAOT 的 5 大根因 + IL 层表现**: 1. **BAML 反射 `Activator.CreateInstance`** —— `callvirt System.Activator::CreateInstance(System.Type)` 2. **DP 注册走 `static readonly` 字段反射** —— 剪裁器看不到运行时枚举 3. **`x:Class` + 事件处理器字符串反射** —— `GetMethod("OnButtonClick", BindingFlags)` 可能剪掉方法 4. **数据绑定 `Path` 字符串反射链** —— `Binding Path=Foo.Bar.Baz` 编译期不可见 5. **样式/模板 `BasedOn`+`StaticResource` 运行时解析** —— 资源键是字符串 - **附加 3 个根因**:附加属性 `GetXxx/SetXxx` 静态方法对反射 / `MarkupExtension` 运行时实例化 / 第三方库未标记 `[RequiresDynamicCode]` - **WPF 应用典型体积表**:最小 Hello World 80 MB → 大型业务 160 MB → 启动时间 1.5-5 s - **3 个替代方案(不放弃 WPF)**: - **方案 1:R2R + Tiered PGO** ✅ 推荐 —— 启动降 30-50%,体积 +30-50%(多架构 PE) - **方案 2:延迟加载 + AssemblyLoadContext 插件化** —— 主 exe 降 40-60%,启动降 20-30% - **方案 3:Self-Contained + Trimmed(仅剪非 WPF)** —— `<PublishTrimmed>true</PublishTrimmed>` **不能用于 WPF**!必须 `TrimMode=link` 或仅剪业务程序集 + `[RequiresDynamicCode]` 标注 - **XAML 2006 vs XAML 2009**:2006 是默认(设计器友好 + BAML 支持)/ 2009 支持 `x:Reference` 双向绑定 / 泛型 `x:Array` / 内置 `MarkupExtension`,但 **BAML 不支持**(只能用反射 XAML) - **Milcore 关键认知**:C++ COM 组件跨进程 IPC + Direct3D 渲染 + 软件回退。Managed 层只是 `SafeHandle` 包装,渲染问题 90% 在 Milcore 层无法直接调试 - **关联知识网络**: - **Day 6 反射** → BAML 解析 `Activator.CreateInstance` 是 NativeAOT 失败根因 - **Day 8 GC** → Milcore 跨进程 = 托管堆与原生堆分离,需 `SafeHandle` 管理生命周期 - **Day 9 JIT** → BAML setter 调用需 JIT 内联,冷启动慢(占 15-20%),R2R 直接消除 - **Day 11 ThreadPool** → `DispatcherSynchronizationContext` 捕获让 await 自动回 UI 线程 - **Day 13 Channel** → 跨线程给 WPF 控件赋值 = `Channel<T>` + `Dispatcher.BeginInvoke` - **未讲 Day 15 依赖属性** → DP 注册是 WPF 反射密集的第二大根因 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 依赖属性(DependencyProperty)核心 —— Day 15(CLR 属性 vs DP / GetValue/SetValue 反射 / `DependencyProperty.Register` 注册 / 元数据继承 / 值变更回调 / `CoerceValueCallback` / `ValidateValueCallback` / 强制值 / 属性值优先级 / `ClearValue` / WPF DP 系统全景 + 数据绑定 / 样式 / 动画如何依赖 DP) --- ## 📊 Day 17 教学记录(详细) ### 今日主题 WPF 视觉树与逻辑树(Visual Tree vs Logical Tree / `VisualTreeHelper` / `LogicalTreeHelper` / `OnApplyTemplate` / `FindAncestor` 缓存 / `VisualBrush` / 命中测试 / `ChartCard` 数据可视化实战) ### 教学重点 - **核心抽象**:WPF 所有 UI 元素**同时存在两棵树**里——逻辑树(XAML 声明的层级,开发者意图结构)和视觉树(实际渲染的层级,含模板内部所有元素)。两棵树**正交分离**,走完全不同的遍历路径 - **两棵树正交分离的设计动机**: - **单一职责 + 关注点分离**:逻辑树 = DataContext 继承 / 资源查找 / 容器识别;视觉树 = 渲染 / 命中测试 / 事件传播 - **同一逻辑节点视觉树可完全不同**:同一 `Button` 在 XP 主题 = 12 个视觉子节点 / Aero = 20 个 / 自定义模板 = N 个 - **Day 16 路由事件关键洞察**:事件沿视觉树传播 / 绑定沿逻辑树解析 `DataContext` —— 两条完全不同的路径 - **4 大核心 API 对比表**: | API | 命名空间 | 时间复杂度 | 原理 | |-----|---------|----------|------| | `VisualTreeHelper.GetParent(v)` | `System.Windows.Media` | **O(1)** | 字段查找(`Visual._parent`) | | `VisualTreeHelper.GetChild(v, i)` | 同上 | O(1) | 索引访问 | | `LogicalTreeHelper.GetParent(e)` | `System.Windows` | **O(深度)** | 递归向上找下一个 `FrameworkElement` | | `LogicalTreeHelper.GetChildren(e)` | 同上 | O(子节点数) | 枚举 | ⚠️ `LogicalTreeHelper.GetParent` 对 `ContentElement`(如 Run/Hyperlink)必须跨层找 `FrameworkElement` - **`FindAncestor<T>` 生产级实现 + 缓存优化**: - ❌ 反例:每次调用从头递归(O(深度)) - ✅ 生产级:`ConditionalWeakTable<DependencyObject, AncestorMap>` 弱引用键缓存 → 命中 O(1),首次 O(深度) - **为什么用 `ConditionalWeakTable`**:普通 `Dictionary` 强引用祖先 → 元素已视觉上离开但缓存还在 → **内存泄漏** - **`OnApplyTemplate` 模板入口铁律**: - `GetTemplateChild` **只能在 `OnApplyTemplate` 内调用**(模板懒加载) - 必须 `base.OnApplyTemplate()` 触发模板事件 - 主题切换 → 旧模板视觉子树销毁 → **新模板视觉子树创建** → **旧引用悬空** → **OnApplyTemplate 再次调用** - **修复**:顶部先 `RemoveHandler` + 清字段 + 重新拿引用 - **`LogicalTreeHelper.GetParent(模板元素)` 返回 null 根因**: - 模板内部实例化的元素(`PART_*`)**只属于视觉树**,不进逻辑树 - `LogicalTreeHelper` 沿逻辑树向上找父 → 模板元素不在逻辑树 → 返回 null - **正确做法**:`VisualTreeHelper.GetParent` 拿视觉父级 - **`VisualBrush` 双刃剑**: - ✅ 便利:把任意 `Visual` 当画刷用(缩略图 / 反射效果 / 实时预览) - ❌ 风险:`new VisualBrush(this)` 强制 WPF **持续实时渲染**源 Visual → 高频更新场景 CPU 80%+ - **生产修复**:`RenderTargetBitmap.Render(this)` 一次性截图 → `ImageBrush` 零持续开销 - **`ChartCard` 完整生产代码核心点**: - 静态 `DefaultStyleKeyProperty.OverrideMetadata` 指定默认模板(Control 基类不知道) - `Loaded += OnLoadedOnce` 一次性访问 `VisualTreeHelper.GetChild(_plotArea!, 0)` 拿 `ItemsControl` - `_itemsControl.AddHandler(MouseMove, ..., handledEventsToo: true)` 即使子元素 Handled 也能收到 - `_itemsControl.InputHitTest(pos) as Ellipse` + `Ellipse.Tag` 绑业务数据 - `PointToScreen`/`PointFromScreen` tooltip 跨窗口定位 - `OnMouseLeftButtonUp` + `e.OriginalSource as Ellipse` + `e.Handled=true` 阻止冒泡拖动 - **5 大生产陷阱**: - **T1:VisualBrush 强制实时渲染**(CPU 80%+)→ 改 `RenderTargetBitmap` 一次性 - **T2:OnApplyTemplate 缓存悬空引用**(主题切换崩)→ 顶部 `RemoveHandler` + 清字段 - **T3:VirtualizingStackPanel 命中失效**(屏幕外元素未实例化)→ 数据点 < 500 用 `StackPanel` / > 500 用 `Canvas` / > 10000 用 LOD - **T4:DataContext 沿逻辑树污染模板元素** → 显式 `DataContext="{x:Null}"` - **T5:路由事件 Handled 提前阻止命中** → 父级 `AddHandler(..., handledEventsToo: true)` 仍能接收 - **关联知识网络**: - **Day 14 WPF 架构** → `Visual` 是 `WindowsBase` + `PresentationCore` 层核心抽象 - **Day 15 DP** → 视觉树遍历找祖先 DP 时,`Inherits=true` 的 DP 沿视觉树向下传(如 `FontSize`) - **Day 16 路由事件** → 事件沿视觉树传播是核心机制,`FindAncestor` + `OriginalSource` 配合精确定位 - **未讲 Phase 4 数据绑定** → `Binding` 沿逻辑树解析 `DataContext`,与视觉树路径完全不同 - **未讲 Phase 4 模板** → `ControlTemplate` 视觉子树是 OnApplyTemplate 展开,`DataTemplate` 是 ItemsControl 容器展开 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 布局系统(Measure / Arrange 两遍布局 / `MeasureOverride` + `ArrangeOverride` / `Panel` 派生 / `Arrange` vs `Render` / `InvalidateMeasure` vs `InvalidateArrange`)—— Day 18 --- ## 📊 Day 18 教学记录(详细) ### 今日主题 WPF 布局系统(Layout System)核心(Measure/Arrange 两遍机制 / 4 个核心类 / 11 步管线 / DesiredSize vs RenderSize / InvalidateMeasure vs InvalidateArrange / WrapPanel 完整生产代码 + 5 大陷阱 + 3 个扩展思考) ### 教学重点 - **核心抽象**:WPF 布局系统 = Measure(父问子"你想要多大")+ Arrange(父给子"你最终多大")两遍机制,由 UIElement / FrameworkElement / Panel / Visual 4 个核心类协作完成 - **11 步布局管线**:child.Measure → MeasureCore → MeasureOverride → DesiredSize → 父收集 → child.Arrange → ArrangeCore → ArrangeOverride → 渲染 → RenderSize → OnRender。`MeasureCore` / `ArrangeCore` 是 sealed,派生类只能重写 MeasureOverride / ArrangeOverride - **4 大铁律**:Measure 在 Arrange 前完成整棵子树 / Arrange 时 DesiredSize 已定可参考 / Measure 多次 Arrange 一次 / `RenderSize ≠ DesiredSize`(Arrange 可强制小于触发裁剪) - **DesiredSize vs RenderSize 5 维对比**:含义(最小需求 vs 最终结果)/ 时机(Measure 后 vs Arrange 后)/ 决定者(子 vs 父)/ 作用(Arrange 矩形输入 vs Render 区域)/ 异常(不可能 vs 可裁剪) - **InvalidateMeasure vs InvalidateArrange 6 维对比**:触发条件(DesiredSize 变 vs RenderSize 变但 DesiredSize 不变)/ 重跑范围(M+A 全树 vs 只 A)/ 性能(重 vs 轻)/ 场景(Text 变 vs 对齐变)/ API(InvalidateMeasure() vs InvalidateArrange())/ 实际触发(UpdateLayout() vs 只排) - **WPF 6 大内置 Panel 策略表**:StackPanel(单向累加)/ WrapPanel(流式换行)/ DockPanel(剩余空间分配)/ Grid(行列切片)/ UniformGrid(等分 N×M)/ Canvas(不测量 + 显式坐标)。⚠️ Canvas 是唯一不测量的 Panel,子元素 DesiredSize 完全由自己决定 - **DP AffectsMeasure / AffectsArrange API**:Width/Height/Margin AffectsMeasure,HorizontalAlignment/VerticalAlignment AffectsArrange,Visibility=Visible→Collapsed AffectsMeasure,Visible→Hidden AffectsArrange - **6 大误区**:T1 MeasureOverride 内改子属性栈溢出 / T2 ArrangeOverride 调 DesiredSize 当最终大小(子未 Measure → 0×0)/ T3 忘 base.MeasureOverride(Panel 不需要,Control 需要)/ T4 DesiredSize==RenderSize 误解 / T5 OnRender 内测量死循环 / T6 不重写 ArrangeOverride 子元素 0,0 堆叠 - **Q3 WrapPanel 完整 300 行生产代码**:Orientation DP FrameworkPropertyMetadataOptions.AffectsMeasure / MeasureOverride 传 Infinity 让子元素自决 / Margin.Left+Right+Top+Bottom 完整处理 / 换行算法 lineSize 累积 + 超出换行 + 提交最后一行 / 4 个场景分析:Margin 完整累加 / Orientation isHorizontal 分支 / 缓存不缓存(MeasureOverride 本质无状态)/ 虚拟化需继承 VirtualizingPanel + 重写 OnItemsChanged - **5 大生产陷阱 T1-T5**:T1 改子属性栈溢出 → 只读 DesiredSize / T2 DP 缺 AffectsMeasure UI 不刷 → FrameworkPropertyMetadataOptions.AffectsMeasure / T3 ArrangeOverride 内 Measure 重布局 → Measure 在 MeasureOverride 内 / T4 VirtualizingPanel 漏 OnItemsChanged → 重写 + InvalidateMeasure / T5 WrapPanel 类 Grid 列宽无限大 → 显式 Width 或 HorizontalAlignment=Left - **3 个扩展思考**:FlexPanel vs WrapPanel 性能(O(N×K) vs O(N))/ 浮点精度换行阈值(> availableSize.Width + 0.5 容忍 0.5px)/ DPI 125% 设备无关像素(1 DIP = 1.25 设备像素) - **关联知识网络**:Day 14 WPF 架构(Panel 在 PresentationFramework)/ Day 15 DP(Width/Height/Margin/AffectsMeasure)/ Day 16 路由事件(LayoutUpdated 是路由事件)/ Day 17 视觉树(Measure/Arrange 沿视觉树遍历)/ 未讲 Phase 4 动画(Storyboard 改 DP 触发 InvalidateMeasure)/ 未讲 Phase 4 绑定(Binding 改 DP 触发重布局)/ 未讲 Phase 4 自定义控件(重写 MeasureOverride 必须 base)/ 未讲 Phase 4 渲染优化(DrawingVisual + VisualHost 绕过布局系统) ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 资源系统(StaticResource / DynamicResource / ResourceDictionary / MergedDictionaries / 资源查找层级 + FindResource vs TryFindResource / 隐式样式 Style 无 Key 自动应用)—— Day 19 --- ## 📊 Day 22 教学记录(详细) ### 今日主题 WPF 样式与模板系统(Style 4 大核心 / ControlTemplate 5 大机制 / DataTemplate 5 大机制 / ItemsPanelTemplate / TemplateBinding 编译期优化 10-50x / 4 大触发器 / Style Trigger vs VSM / ValidationIndicator 完整生产实战 + 5 大生产陷阱 + 7 大易错点 + 3 个动手扩展思考 + 8 道自检题) ### 教学重点 - **核心 4 大革命**:WinForms 做不到的主题化 / 控件外观完全重定义 / 数据-视图分离 / 多状态自动响应 → WPF Style + ControlTemplate + DataTemplate + 触发器全解决 - **Style 4 大核心属性**:TargetType(必需,Setter 只接受 TargetType 上的属性,Day 15 DP 关联)/ BasedOn(链式继承)/ Resources(私有字典避免全局污染)/ Setters(含 Style.Triggers) - **Style 选择器层级**:隐式样式(无 x:Key,按 TargetType 自动应用所有实例)/ 显式样式(有 x:Key,需手动 StaticResource 引用)/ 元素局部样式(直接 Style="...")→ 优先级铁律(局部 > 显式 > 隐式 > 主题默认) - **ControlTemplate 5 大机制**:PART_* 命名约定(Code 与 Template 的契约,Day 17 关键点)/ TemplateBinding 编译期优化(直接订阅 PropertyChanged 跳过 BindingExpression 反射链,性能 10-50x)/ ContentPresenter 自动应用(ContentSource 属性指定显示哪个 DP)/ 触发器与状态(ControlTemplate.Triggers 旧式 + VisualStateManager VSM 新式推荐)/ OverridesDefaultStyle(完全跳过主题默认模板) - **DataTemplate 5 大机制**:DataType 自动选择(按运行时类型自动套模板,比 Style.TargetType 更精细)/ DataTemplateSelector 运行时选择(根据数据属性选不同模板)/ HierarchicalDataTemplate 树形递归(TreeView 专用,HeaderedItemsControl 用 ItemsPresenter 而非 ContentPresenter)/ ContentPresenter 自动应用(5 大隐式应用场景)/ ItemTemplate 列表项模板(ListBox/ComboBox 等 ItemsControl) - **ItemsPanelTemplate 4 大实战场景**:WrapPanel 标签云流式布局 / Canvas 画板自由定位 / UniformGrid 启动器等分 / VirtualizingStackPanel 大数据虚拟化 - **TemplateBinding 编译期优化根因**:XAML 编译期创建轻量 TemplateBindingExpression,直接订阅 TemplatedParent 的 PropertyChanged 并 SetValue 到目标元素;vs `{Binding RelativeSource=TemplatedParent}` 走完整 BindingExpression 管线(反射解析 Path + 订阅 + UpdateTarget + SetValue),实测 1000 次模板创建 5ms vs 250ms = **50 倍性能差** - **TemplateBinding 5 大限制**:单向(不能 TwoWay)/ DataTemplate 内不可用(要 DataContext)/ 无 IValueConverter(需转换走 Binding)/ 不能在 Style Trigger 内(Trigger Setter 只能用 StaticResource)/ 不暴露 BindingExpression 事件(拦截不了 TargetUpdated) - **4 大触发器完整对比**:Trigger 单 DP 属性 / MultiTrigger 多 DP 属性 AND / DataTrigger 绑定数据值(要求 INPC)/ MultiDataTrigger 多绑定数据 AND / EventTrigger 路由事件启动 Storyboard - **Style Trigger vs VSM 6 维对比**:出现版本(3.0 vs 4.0)/ 状态模型(离散条件 vs 状态组)/ 过渡动画(瞬间切换 vs Storyboard Transition)/ 自定义控件模板(简单场景 vs 复杂控件 Modern WPF)/ 触摸键盘焦点(不感知 vs 状态组全覆盖) - **关联知识网络**: - Day 15 DP → Style Setter 必须是 TargetType 上的 DP,TemplateBinding 同理 - Day 17 视觉树 → ControlTemplate 内的所有元素都是视觉子树成员,OnApplyTemplate 才能拿 PART_* - Day 18 布局 → DataTemplate 内的布局走完整 Measure/Arrange 管线 - Day 19 资源系统 → Style/DataTemplate 都定义在 ResourceDictionary 里,隐式样式走合并字典后覆盖前 - Day 20 数据绑定 → TemplateBinding 是 Binding 的子集(编译期优化版) - Day 21 命令系统 → ControlTemplate 内的 Button.Command 通过 TemplateBinding 自动连到父级 - **Q1 完整方案**:4 大核心职责 + 完整协作关系图(Style → Setter Template→ControlTemplate + Setter ContentTemplate/ItemTemplate→DataTemplate + ItemsPanel→ItemsPanelTemplate)+ 5 大隐式应用场景 + 4 大实战场景 - **Q2 完整方案**:TemplateBinding vs Binding RelativeSource=TemplatedParent 6 维对比表 + TemplateBindingExpression 伪代码 + 性能实测 50x + 5 大限制 + HierarchicalDataTemplate 递归原理 + 4 种循环引用防御方案 + 5 易错点 - **Q3 完整 ValidationIndicator 生产代码**:静态 DefaultStyleKeyProperty.OverrideMetadata + 3 个 PART_* 命名常量 + OnApplyTemplate 顶部清理旧引用防 T2 主题切换 + IsValid DP FrameworkPropertyMetadataOptions.AffectsRender + Tooltip 动态更新 + Themes/Generic.xaml 4 状态 DataTrigger 用 RelativeSource Self + x:Static 引用枚举值 + TargetName 限定 Setter + 5 大生产陷阱完整错误示例与修复 T1-T5 + 7 大易错点 + 3 个动手实践 + 8 道自检题 ### 主人反馈 - _等待主人回复_ ### 主人答案评估 - _等待主人回复_ ### 下次起点 自定义控件开发(Generic.xaml 全套主题 / 自定义控件 vs 用户控件区别 / 控件继承链选择 / 自定义路由事件 / 自定义 DP / OnRender 绘制 vs VisualHost / 完整 ToggleSwitch 开关控件生产实战 + 5 大生产陷阱)—— Day 23 --- _严老师专用 · 主人 + 小 Q 共同维护 · 2026-08-22 v1.22(Day 22 推送完成,Phase 3 第 9 课,等待主人回复)_

📡 推送信息

教学日期
2026-07-27
所属阶段
首次教学 · 资深起点确认
消息条数
0 条(飞书 DM teacher-yan-bot 推送)
消息 ID
数据来源
memory_summary_fallback