15 KiB
15 KiB
WPF Code Guidelines
- Use modern C# and current .NET/WPF conventions.
- Keep UI, application logic and domain logic clearly separated.
- Prefer MVVM for non-trivial views.
- Keep code-behind minimal.
- Use code-behind only for view-specific behavior that does not belong in the ViewModel.
- Avoid business logic in views.
- Avoid direct service access from controls.
- Keep dependencies explicit and testable.
- Prefer composition over inheritance.
- Avoid unnecessary abstractions and framework wrappers.
- Follow the existing project architecture unless there is a strong reason to change it.
MVVM
- Keep ViewModels focused on presentation state and commands.
- Do not put UI element references into ViewModels.
- Do not access
Window,UserControl,DispatcherObjector visual tree elements from ViewModels. - Avoid direct navigation, dialogs or message boxes from ViewModels without an abstraction when testability matters.
- Prefer explicit application services for navigation, dialogs and external interactions.
- Keep domain logic outside ViewModels.
- Do not use ViewModels as general-purpose service containers.
- Keep ViewModel dependencies minimal.
- Prefer immutable or readonly state where practical.
- Avoid duplicated derived state.
- Keep state transitions predictable.
Data Binding
- Prefer data binding over manual UI synchronization.
- Use strongly typed properties.
- Use appropriate binding modes.
- Prefer
OneWaywhen two-way updates are not required. - Use
TwoWayonly when the UI must modify the source. - Avoid unnecessary
UpdateSourceTrigger=PropertyChangedfor expensive operations. - Keep binding paths simple and stable.
- Avoid deeply nested binding paths.
- Do not hide broken bindings.
- Treat binding errors as real defects.
- Use
FallbackValueandTargetNullValueonly when they reflect intended behavior. - Avoid excessive converters.
- Prefer exposing correctly shaped state from the ViewModel over complex converter logic.
- Avoid
MultiBindingwhen simpler state modeling is clearer. - Do not put business logic into converters.
Property Change Notifications
- Implement property change notifications consistently.
- Avoid raising
PropertyChangedunnecessarily. - Raise notifications only when values actually change.
- Keep dependent property notifications explicit.
- Avoid hidden notification chains that are difficult to reason about.
- Prefer reusable base implementations only when they reduce duplication without obscuring behavior.
- Do not use reflection-based notification mechanisms unless there is a concrete benefit.
Commands
- Prefer commands over click handlers for ViewModel-driven actions.
- Keep command execution focused.
- Avoid putting large workflows directly inside command implementations.
- Keep
CanExecutelogic cheap. - Refresh command availability only when relevant state changes.
- Avoid command implementations with hidden side effects.
- Handle async commands safely.
- Prevent accidental duplicate execution when an async action must not run concurrently.
- Never use
async voidexcept for true event handlers.
Async and Threading
- Keep async operations asynchronous throughout the call chain.
- Avoid
.Result,.Wait()and other synchronous blocking of async work. - Do not block the UI thread.
- Run I/O asynchronously.
- Move expensive CPU work off the UI thread only when necessary.
- Marshal UI updates back to the UI thread when required.
- Keep Dispatcher usage minimal and explicit.
- Do not use the Dispatcher to hide incorrect threading design.
- Support
CancellationTokenfor cancellable long-running operations. - Cancel obsolete work when views or selections change.
- Prevent race conditions caused by overlapping async operations.
- Handle exceptions from background operations explicitly.
UI Thread
- Treat the UI thread as a scarce resource.
- Avoid expensive loops, parsing, serialization, database access or network calls on the UI thread.
- Avoid excessive Dispatcher invocations.
- Batch UI updates where practical.
- Do not update UI-bound collections excessively in tight loops.
- Avoid synchronous waits on background work.
- Keep rendering-related callbacks lightweight.
Collections
- Use
ObservableCollection<T>only when collection change notifications are required. - Do not use
ObservableCollection<T>as a general-purpose collection. - Use
List<T>or other suitable collections for non-observable internal data. - Avoid replacing entire observable collections when incremental updates are sufficient.
- Avoid thousands of individual collection notifications when a bulk update is more appropriate.
- Keep collection mutations on the correct thread.
- Avoid exposing mutable internal collections unnecessarily.
- Prefer read-only views where consumers should not modify data.
XAML
- Keep XAML readable and focused.
- Avoid excessively large view files.
- Split complex views into focused controls when it improves maintainability.
- Use resources for repeated values and styles.
- Avoid magic numbers in XAML.
- Keep styles and templates consistent.
- Avoid excessive inline styling.
- Prefer reusable resources over duplicated markup.
- Avoid deeply nested panels.
- Keep visual trees as shallow as practical.
- Use semantic control choices.
- Avoid unnecessary containers.
- Do not overuse triggers when simpler state representation is possible.
- Keep templates understandable.
- Avoid XAML tricks that make behavior hard to discover.
Layout
- Prefer
Gridfor structured layouts. - Use
StackPanelonly where its layout behavior is actually appropriate. - Avoid unnecessary nested panels.
- Avoid fixed sizes unless the design requires them.
- Prefer responsive sizing with
Autoand*. - Avoid layout configurations that cause excessive measure/arrange passes.
- Be careful with large visual trees inside scrolling containers.
- Avoid disabling virtualization accidentally.
Virtualization
- Keep UI virtualization enabled for large collections.
- Do not use container layouts that disable virtualization without a reason.
- Avoid wrapping large virtualized item lists in additional scrolling containers.
- Use virtualizing panels where appropriate.
- Verify virtualization when displaying large data sets.
- Avoid rendering thousands of controls simultaneously.
- Prefer paging, incremental loading or virtualization for very large data sets.
Resources and Styles
- Use shared resources for repeated styles, brushes, templates and dimensions.
- Keep resource dictionaries focused.
- Avoid giant global resource dictionaries.
- Prefer feature-level resources when styles are not truly global.
- Use
StaticResourceby default. - Use
DynamicResourceonly when runtime resource replacement is required. - Avoid excessive dynamic resource lookups.
- Keep theme resources separated from application logic.
- Avoid hardcoded colors and dimensions when design tokens or resources already exist.
Dependency Properties
- Use dependency properties only when WPF property-system behavior is required.
- Do not use dependency properties as a replacement for normal CLR properties.
- Keep metadata and callbacks lightweight.
- Avoid expensive work in property-changed callbacks.
- Avoid callbacks with hidden side effects.
- Validate or coerce values only when the control contract requires it.
- Do not expose mutable default values in dependency property metadata.
Attached Properties and Behaviors
- Use attached properties and behaviors for reusable view-specific behavior.
- Do not use them to hide business logic.
- Keep behaviors focused and predictable.
- Clean up event handlers when behaviors are detached.
- Avoid global attached-property state.
- Prefer explicit behavior over complex XAML hacks.
Events
- Avoid unnecessary event subscriptions.
- Unsubscribe from long-lived publishers when required.
- Prevent memory leaks from event handlers.
- Prefer weak events where publisher lifetime significantly exceeds subscriber lifetime.
- Keep event handlers small.
- Do not use events as hidden global communication channels.
- Avoid excessive event chaining.
Memory Management
-
Watch for memory leaks caused by:
- Event subscriptions
- Timers
- Static references
- Long-lived services
- Cached views
- Closures
- Collection views
- Binding-related references
-
Dispose resources correctly.
-
Stop timers and background work when no longer needed.
-
Avoid retaining closed windows or unloaded views.
-
Do not keep unnecessary references to visual elements.
-
Profile memory when long-running applications continuously grow.
Windows and Dialogs
- Keep window lifecycle explicit.
- Avoid creating duplicate windows unintentionally.
- Do not keep hidden windows alive without a reason.
- Separate dialog logic from business logic.
- Prefer dialog services when ViewModels need to request user interaction.
- Keep ownership relationships explicit.
- Avoid application shutdown behavior that depends on accidental window lifetime.
Navigation
- Keep navigation state explicit.
- Avoid coupling ViewModels directly to concrete views.
- Avoid service locator patterns for view resolution.
- Keep navigation history intentional.
- Dispose or release navigation targets when they are no longer required.
- Do not retain entire navigation graphs unnecessarily.
Validation
- Validate user input at the appropriate boundary.
- Keep validation logic out of XAML when it becomes complex.
- Prefer reusable validation rules or ViewModel validation for non-trivial cases.
- Display validation errors consistently.
- Do not rely on UI validation as a security boundary.
- Validate again at backend or domain boundaries when required.
- Preserve user input on recoverable errors.
Performance
- Keep the UI thread responsive.
- Minimize visual tree depth.
- Keep virtualization enabled.
- Avoid unnecessary bindings.
- Avoid unnecessary converters.
- Avoid unnecessary resource lookups.
- Avoid unnecessary property change notifications.
- Avoid frequent full collection refreshes.
- Avoid loading large datasets eagerly.
- Avoid loading large images at full resolution when unnecessary.
- Freeze
Freezableobjects where practical. - Reuse immutable resources where appropriate.
- Avoid unnecessary animations.
- Avoid high-frequency timers on the UI thread.
- Measure startup time, UI responsiveness, memory usage and rendering performance before micro-optimizing.
Images and Media
- Load images at an appropriate resolution.
- Avoid decoding images significantly larger than their displayed size.
- Release streams and file handles correctly.
- Avoid locking image files unintentionally.
- Cache images only when there is a clear memory strategy.
- Avoid keeping large bitmaps alive unnecessarily.
- Prefer frozen image resources when they are immutable.
Security
- Treat all external input as untrusted.
- Never expose secrets, credentials or tokens in UI code.
- Do not store secrets in configuration files committed to source control.
- Avoid leaking sensitive information through logs, dialogs or exception messages.
- Validate file paths and external resources.
- Do not execute external processes or files without explicit validation.
- Avoid insecure deserialization.
- Do not weaken TLS, authentication or certificate validation.
- Prefer secure defaults.
- Never disable security controls for convenience.
File and Process Access
- Validate file paths before use.
- Avoid assuming files or directories exist.
- Handle permissions and locked files gracefully.
- Avoid arbitrary shell execution.
- Quote and validate process arguments safely.
- Do not concatenate untrusted input into shell commands.
- Avoid elevated privileges unless explicitly required.
- Clean up temporary files when appropriate.
Error Handling
- Handle expected errors explicitly.
- Do not swallow exceptions.
- Preserve useful exception context.
- Avoid showing raw technical exceptions directly to end users.
- Log enough information for diagnosis without exposing sensitive data.
- Keep user-facing error messages clear and actionable.
- Avoid using exceptions for normal control flow.
- Ensure background exceptions are observed.
Logging
- Use structured logging.
- Avoid excessive logging on the UI thread.
- Do not log every property change or binding update.
- Never log secrets, tokens, credentials or sensitive user data.
- Include relevant context for operational failures.
- Avoid logging the same exception repeatedly at multiple layers.
Architecture
- Keep presentation, application, domain and infrastructure concerns separated.
- Avoid direct database access from views or ViewModels.
- Avoid direct HTTP access from controls.
- Keep external integrations behind focused services.
- Avoid circular dependencies.
- Keep feature boundaries clear.
- Do not create shared utility classes for unrelated functionality.
- Keep shared code genuinely reusable.
- Minimize global state.
- Avoid static service access.
Dependency Injection
- Use dependency injection where lifecycle management, substitution or testability benefits from it.
- Avoid service locator patterns.
- Keep constructor dependencies minimal.
- Do not inject dependencies that are not used.
- Choose service lifetimes intentionally.
- Do not make stateful services global without a concrete reason.
- Avoid introducing interfaces solely for dependency injection when no abstraction is needed.
Testing
- Keep ViewModels testable without a UI thread where practical.
- Test behavior rather than implementation details.
- Test domain and application logic independently from WPF.
- Avoid brittle tests coupled to XAML structure.
- Keep UI automation tests focused on critical flows.
- Mock external boundaries rather than internal details.
- Keep tests deterministic.
- Avoid real network, database or filesystem dependencies in unit tests unless explicitly intended.
Code Quality
- Use nullable reference types.
- Prefer modern, readable C# syntax.
- Avoid unnecessary interfaces and abstractions.
- Prefer concrete types where no abstraction is required.
- Use descriptive names.
- Remove dead code.
- Remove commented-out code.
- Do not add comments unless the user explicitly requests them.
- Treat compiler, analyzer and binding warnings seriously.
- Do not suppress warnings without a concrete reason.
- Prefer correctness, responsiveness, security and maintainability over cleverness.
Performance Priority
Prioritize WPF performance work in this order:
- UI architecture
- UI thread blocking
- Data loading and I/O
- Virtualization
- Visual tree complexity
- Binding and notification frequency
- Rendering and images
- Memory retention and leaks
- Allocations
- CPU micro-optimizations
Never optimize based purely on assumptions.
Safety Rule
- The UI must remain responsive.
- Do not block the UI thread with I/O or expensive CPU work.
- Do not bypass validation or security controls for convenience.
- Do not introduce hidden cross-thread access.
- Prefer predictable, explicit and testable UI behavior.