Learn best practices for developing high-quality Avalonia UI applications including coding standards, architecture patterns, and performance optimization
This guide covers best practices for developing Avalonia applications, including coding standards, architectural patterns, performance optimization, and contribution guidelines based on the Avalonia project itself.
Keep lines of code around 120 characters or less. This is not a hard limit, but try to keep code readable.
// Good: concise and readablepublic void ProcessData(string input, int maxLength, bool validate){ // ...}// Avoid: overly long linespublic void ProcessDataWithMultipleParametersAndAVeryLongMethodNameThatExceedsReadableLength(string input, int maxLength, bool validate, string additionalData)
2
Documentation
All public methods must have XML documentation:
/// <summary>/// Initializes a new instance of the control./// </summary>/// <param name="name">The name of the control.</param>/// <returns>The initialized control instance.</returns>public Control InitializeControl(string name){ // Implementation}
3
Naming conventions
Follow consistent naming:
// Classes and methods: PascalCasepublic class MyCustomControl : Control{ // Private fields: _camelCase private string _internalState; // Public properties: PascalCase public string DisplayName { get; set; } // Methods: PascalCase public void UpdateState() { // Local variables: camelCase var currentValue = GetValue(); }}
4
Avoid regions
DO NOT USE #REGIONS - they hide code organization issues:
// Bad: using regions#region Propertiespublic string Name { get; set; }#endregion// Good: well-organized code without regionspublic string Name { get; set; }public int Age { get; set; }
public class UserViewModel : ViewModelBase{ private string _userName; public string UserName { get => _userName; set => SetProperty(ref _userName, value); } public void ValidateUser() { if (string.IsNullOrEmpty(UserName)) { throw new ArgumentException("User name cannot be empty"); } }}
Use StyledProperty for properties that support styling and binding:
public class MyControl : Control{ public static readonly StyledProperty<string> TitleProperty = AvaloniaProperty.Register<MyControl, string>(nameof(Title)); public string Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }}
Use DirectProperty for performance-critical properties:
public class MyControl : Control{ private int _count; public static readonly DirectProperty<MyControl, int> CountProperty = AvaloniaProperty.RegisterDirect<MyControl, int>( nameof(Count), o => o.Count, (o, v) => o.Count = v); public int Count { get => _count; set => SetAndRaise(CountProperty, ref _count, value); }}
// Good: Describes what is being testedpublic void Calling_Foo_Should_Increment_Bar(){ // Arrange var sut = new MyClass(); // Act sut.Foo(); // Assert Assert.Equal(1, sut.Bar);}// Render tests should describe the outputpublic void Rectangle_2px_Stroke_Filled(){ // Test implementation}
# Good commit messagegit commit -m "Add data validation to UserViewModelImplemented INotifyDataErrorInfo to provide real-timevalidation feedback for user input fields.Fixes #123"# Bad commit messagegit commit -m "fixed stuff"
3
Avoid unrelated changes
Do not change code unrelated to your fix/feature
Do not introduce spurious formatting changes
Do not fix style issues in unrelated code (create separate PRs)
4
Test your changes
Before submitting:
# Run unit testsdotnet test# Build release configurationdotnet build -c Release# Run integration tests if applicablecd tests/Avalonia.IntegrationTests.Appiumdotnet test
During a major release cycle, do not introduce source or binary breaking changes. This is checked by automated tools.
For future breaking changes:
// TODO12: Remove deprecated method in version 12[Obsolete("Use NewMethod instead. This will be removed in v12.")]public void OldMethod(){ NewMethod();}public void NewMethod(){ // New implementation}
public interface ISecureStorage{ Task SetAsync(string key, string value); Task<string> GetAsync(string key);}// Platform-specific implementationpublic class WindowsSecureStorage : ISecureStorage{ // Use Windows Credential Manager}public class MacOSSecureStorage : ISecureStorage{ // Use macOS Keychain}
<Button AutomationProperties.Name="Save Document" AutomationProperties.HelpText="Saves the current document to disk"> <PathIcon Data="{StaticResource SaveIcon}" /></Button>
// Implement AutomationPeer for custom controlsprotected override AutomationPeer OnCreateAutomationPeer(){ return new MyControlAutomationPeer(this);}
/// <summary>/// Represents a custom control for displaying user information./// </summary>/// <remarks>/// This control automatically formats and displays user data/// with support for avatars and status indicators./// </remarks>public class UserInfoControl : Control{ /// <summary> /// Gets or sets the user name to display. /// </summary> /// <value> /// The user name, or <c>null</c> if not set. /// </value> public string UserName { get; set; }}
// Calculate the layout bounds considering the parent's constraints// and the child's desired size. We need to handle three cases:// 1. Child fits within parent constraints// 2. Child exceeds constraints but can be scrolled// 3. Child exceeds constraints and must be clippedvar bounds = CalculateLayoutBounds(parentSize, childSize);