# Performance Optimization 本节定义性能优化策略和最佳实践,确保控件库满足 NFR 性能要求。 --- ### Performance Targets (from NFR) | 指标 | 目标值 | 测量方法 | |------|--------|----------| | UI 渲染帧率 | 60fps (16ms/frame) | Visual Studio Performance Profiler | | 主题切换时间 | < 100ms | Stopwatch 测量 | | PropertyGrid 生成(50 属性) | < 200ms | Stopwatch 测量 | | 应用启动时间 | < 2s | 从启动到窗口显示 | | 内存占用 | < 100MB (空闲时) | Task Manager / dotnet-counters | --- ### UI Rendering Performance **关键原则:避免在 UI 线程执行耗时操作** **❌ 反面示例 - 阻塞 UI 线程:** ```csharp // 错误:在属性 getter 中执行反射 public ObservableCollection Properties { get { var items = new ObservableCollection(); if (SelectedObject != null) { // 耗时操作会阻塞 UI 渲染 var properties = SelectedObject.GetType().GetProperties(); foreach (var prop in properties) { items.Add(CreatePropertyItem(SelectedObject, prop)); } } return items; } } ``` **✅ 正确示例 - 异步加载:** ```csharp private ObservableCollection _properties = new(); public ObservableCollection Properties => _properties; private async void RefreshProperties(object? obj) { _properties.Clear(); if (obj == null) return; // 在后台线程执行反射 var items = await Task.Run(() => { var result = new List(); var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { result.Add(CreatePropertyItem(obj, prop)); } return result; }); // 回到 UI 线程更新集合 foreach (var item in items) { _properties.Add(item); } } ``` --- ### Reflection Optimization **问题:反射是 PropertyGrid 的性能瓶颈** **优化策略 1:缓存 PropertyInfo** ```csharp private static readonly ConcurrentDictionary _propertyCache = new(); private PropertyInfo[] GetCachedProperties(Type type) { return _propertyCache.GetOrAdd(type, t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance) ); } ``` **优化策略 2:缓存 Attribute 读取** ```csharp private static readonly ConcurrentDictionary _categoryCache = new(); private string? GetCategory(PropertyInfo prop) { var attr = _categoryCache.GetOrAdd(prop, p => p.GetCustomAttribute() ); return attr?.Category; } ``` **优化策略 3:使用编译表达式替代反射调用(可选)** ```csharp // 反射调用(慢) var value = propertyInfo.GetValue(obj); // 编译表达式(快 10-100 倍) private static Func CreateGetter(PropertyInfo prop) { var instance = Expression.Parameter(typeof(object), "instance"); var convert = Expression.Convert(instance, prop.DeclaringType!); var property = Expression.Property(convert, prop); var convertResult = Expression.Convert(property, typeof(object)); return Expression.Lambda>(convertResult, instance).Compile(); } ``` **性能对比:** ``` 反射 GetValue: ~1000 ns/调用 编译表达式: ~10 ns/调用 直接属性访问: ~1 ns/调用 ``` --- ### Theme Switching Performance **优化主题切换速度(目标 < 100ms):** **❌ 反面示例 - 逐个替换资源:** ```csharp // 错误:逐个替换资源会触发多次 UI 刷新 foreach (var key in themeColors.Keys) { Application.Current.Resources[key] = themeColors[key]; } ``` **✅ 正确示例 - 批量替换 ResourceDictionary:** ```csharp public static void ApplyTheme(ThemeType type) { var app = Application.Current; if (app == null) return; // 移除旧主题 var oldTheme = app.Resources.MergedDictionaries .FirstOrDefault(d => d.Source?.ToString().Contains("Theme") == true); if (oldTheme != null) app.Resources.MergedDictionaries.Remove(oldTheme); // 一次性加载新主题(单次 UI 刷新) var uri = type == ThemeType.Light ? new Uri("avares://Penguin.AvaloniaUI/Themes/LightTheme.axaml") : new Uri("avares://Penguin.AvaloniaUI/Themes/DarkTheme.axaml"); var newTheme = new ResourceDictionary { Source = uri }; app.Resources.MergedDictionaries.Add(newTheme); _currentTheme = type; ThemeChanged?.Invoke(null, type); } ``` **预加载主题资源(可选优化):** ```csharp private static ResourceDictionary? _cachedLightTheme; private static ResourceDictionary? _cachedDarkTheme; static ThemeManager() { // 应用启动时预加载主题 _cachedLightTheme = LoadThemeResource(ThemeType.Light); _cachedDarkTheme = LoadThemeResource(ThemeType.Dark); } public static void ApplyTheme(ThemeType type) { var theme = type == ThemeType.Light ? _cachedLightTheme : _cachedDarkTheme; // 直接使用缓存的主题,无需重新加载 } ``` --- ### Memory Management **避免内存泄漏的常见陷阱:** **问题 1:事件订阅未取消** ```csharp // ❌ 可能导致内存泄漏 public class PropertyGrid : TemplatedControl { public PropertyGrid() { ThemeManager.ThemeChanged += OnThemeChanged; // 没有取消订阅! } private void OnThemeChanged(object? sender, ThemeType type) { RefreshTheme(); } } // ✅ 正确实现 IDisposable public class PropertyGrid : TemplatedControl, IDisposable { public PropertyGrid() { ThemeManager.ThemeChanged += OnThemeChanged; } public void Dispose() { ThemeManager.ThemeChanged -= OnThemeChanged; } private void OnThemeChanged(object? sender, ThemeType type) { RefreshTheme(); } } ``` **问题 2:ObservableCollection 过度使用** ```csharp // ❌ 每次都创建新集合 public ObservableCollection Properties { get => new ObservableCollection(GetProperties()); } // ✅ 复用同一个集合 private ObservableCollection _properties = new(); public ObservableCollection Properties => _properties; private void RefreshProperties() { _properties.Clear(); foreach (var item in GetProperties()) { _properties.Add(item); } } ``` --- ### Virtualization (Post-MVP) **如果 PropertyGrid 需要支持数百个属性,使用虚拟化:** ```xml ``` **虚拟化性能对比:** ``` 100 个属性(非虚拟化): ~300ms 生成 + 50MB 内存 100 个属性(虚拟化): ~100ms 生成 + 10MB 内存 ``` --- ### Avoid Common Pitfalls **陷阱 1:在循环中触发属性变化通知** ```csharp // ❌ 每次循环触发 UI 刷新 for (int i = 0; i < 1000; i++) { Properties.Add(new PropertyItem()); // 触发 CollectionChanged } // ✅ 批量添加后触发一次刷新 var items = new List(); for (int i = 0; i < 1000; i++) { items.Add(new PropertyItem()); } Properties.Clear(); foreach (var item in items) { Properties.Add(item); } ``` **陷阱 2:频繁的字符串拼接** ```csharp // ❌ 每次拼接创建新字符串 string result = ""; for (int i = 0; i < 100; i++) { result += properties[i].Name + ", "; } // ✅ 使用 StringBuilder var sb = new StringBuilder(); for (int i = 0; i < 100; i++) { sb.Append(properties[i].Name).Append(", "); } var result = sb.ToString(); ``` **陷阱 3:XAML 中的复杂绑定** ```xml ``` --- ### Performance Measurement **使用 Stopwatch 测量关键操作:** ```csharp #if DEBUG private void RefreshProperties(object? obj) { var sw = Stopwatch.StartNew(); // 核心逻辑 _properties.Clear(); var items = GetProperties(obj); foreach (var item in items) { _properties.Add(item); } sw.Stop(); Debug.WriteLine($"[PropertyGrid] RefreshProperties took {sw.ElapsedMilliseconds}ms for {items.Count} properties"); } #endif ``` **Visual Studio Performance Profiler:** 1. 调试 → 性能探查器 2. 选择"CPU 使用情况"或".NET 对象分配跟踪" 3. 启动分析 4. 执行性能关键操作(如生成 50 个属性) 5. 停止分析并查看热点代码 **dotnet-counters 监控内存:** ```bash # 安装工具 dotnet tool install --global dotnet-counters # 监控运行中的应用 dotnet-counters monitor --process-id --counters System.Runtime ``` --- ### Optimization Checklist **开发时检查清单:** - [ ] 反射操作已缓存(PropertyInfo、Attribute) - [ ] 耗时操作(> 50ms)在后台线程执行 - [ ] 事件订阅在控件销毁时取消 - [ ] ObservableCollection 复用而非重建 - [ ] 批量更新集合,避免频繁触发 CollectionChanged - [ ] 字符串拼接使用 StringBuilder - [ ] XAML 绑定避免复杂转换器 - [ ] 主题切换批量替换 ResourceDictionary **发布前性能测试:** - [ ] PropertyGrid 50 属性生成 < 200ms - [ ] 主题切换 < 100ms - [ ] 应用启动 < 2s - [ ] 空闲时内存 < 100MB - [ ] 无明显的 UI 卡顿(帧率 > 50fps) ---