Our website uses cookies to enhance your browsing experience.
Accept
to the top
>
>
>
Expedition into Avalonia project

Expedition into Avalonia project

Jul 07 2025

In the age of UI frameworks, legends tell about the great land where a single codebase rules all platforms. Today, we set foot on the shores of the abandoned mainland known as Avalonia.

The mainland of Avalonia is divided into six regions: the Kingdom of Windows, the Free Territories of Linux, the Mountain Strongholds of macOS, the Mobile Principalities of Android, the Coastal Domains of iOS, and the Foggy Archipelago of WebAssembly. Let's equip ourselves with a static analysis compass and keep a detailed journal of our discoveries.

About project

Avalonia is an open-source, cross-platform UI framework for creating applications using .NET on Windows, macOS, Linux, iOS, Android, and WebAssembly.

The framework uses its cross-platform rendering engine to display UI elements, ensuring consistent appearance and behavior across all supported platforms. This means that developers can share user interface code and maintain a consistent style and functionality across the target platform.

Avalonia unifies desktop, mobile, and web development using a unique approach that differs from traditional cross-platform frameworks. Instead of using native UI elements, Avalonia implements its cross-platform rendering engine, which provides pixel-perfect accuracy on all supported platforms.

Note. We checked this project in 2019. You can read the article about it here.

Exploring Avalonia

We checked the Avalonia plugin for VS Code. You can read how to use it in this documentation.

Sacred knowledge of the mainland

Experienced Avalonia guides know clever tricks to shorten their journey. We'll gather secrets about the use of patterns in C#.

Let's take a peek at the is not null or construction.

Issue 1

public PanelContainerGenerator(ItemsPresenter presenter)
{
  Debug.Assert(presenter.ItemsControl is not null);
  Debug.Assert(presenter.Panel is not null or VirtualizingPanel);
  
  ....
}

The PVS-Studio warning: V3207 The 'not null or VirtualizingPanel' logical pattern may not work as expected. The 'not' pattern is matched only to the first expression from the 'or' pattern. PanelContainerGenerator.cs 22

The issue arises because devs have forgotten that the not operator has a higher precedence than or. In this construction, the second sub-expression of the or operator becomes meaningless. For example, in presenter.Panel is not null or VirtualizingPanel, if presenter.Panel is null, checking for null will return true. As a result, the second part of the expression has no effect on the final outcome.

To ensure correct code execution, developers can rewrite the code as follows:

public PanelContainerGenerator(ItemsPresenter presenter)
{
  Debug.Assert(presenter.ItemsControl is not null);
  Debug.Assert(presenter.Panel is not (null or VirtualizingPanel));

  ....
}

Mirages: where parameters are altered

On the borders between regions of Avalonia, strange things can happen—passed parameters, like mirages, become distorted and change their meaning. In this section, we'll look at cases where a method parameter errors.

Issue 2

protected override Size MeasureOverride(Size availableSize)
{
  availableSize = new Size(double.PositiveInfinity,
                           double.PositiveInfinity);

  foreach (Control child in Children)
  {
    child.Measure(availableSize);
  }

  return new Size();
}

The PVS-Studio warning: V3061 Parameter 'availableSize' is always rewritten in method body before being used. Canvas.cs 149

In this example, a parameter is passed to the method but then immediately overwritten. Looking at the code, we can suggest that the method doesn't need the parameter being passed, or the method's logic is a little messed up.

Issue 3

public static async Task<string> Load(string generatedCodeResourceName)
{
  var assembly = typeof(XamlXNameResolverTests).Assembly;
  var fullResourceName = assembly
      .GetManifestResourceNames()
      .First(name => name.Contains("InitializeComponent")
                  && name.Contains("GeneratedInitializeComponent") 
                  && name.EndsWith(generatedCodeResourceName));

  ....
}

The PVS-Studio warning: V3053 An excessive expression. Examine the substrings 'InitializeComponent' and 'GeneratedInitializeComponent'. InitializeComponentCode.cs 27

The analyzer highlights a case where the InitializeComponent and GeneratedInitializeComponent strings are passed to the Contains method. But the first string is a substring of the second one. So, a call to Contains with the second string doesn't impact the condition result.

Traps along the path of choices

At the crossroads of Avalonia's paths, stone markers point the way to travelers. But some of them deceive us! In this section, we'll explore common mistakes in the project's conditional expressions.

Issue 4

public override void Promote(FrugalListBase<T> oldList)
{
  for (var index = 0; index < oldList.Count; ++index)
  {
    if (FrugalListStoreState.Success == Add(oldList.EntryAt(index)))
    {
      continue;
    }

    // this list is smaller than oldList
    throw new ArgumentException($"Cannot promote from '{oldList}' 
      to '{ToString()}'    because the target map is too small.",
      nameof(oldList));
  }
}

The PVS-Studio warning: V3022 Expression 'FrugalListStoreState.Success == Add(oldList.EntryAt(index))' is always true. FrugalList.cs 1473

The analyzer reports that the FrugalListStoreState.Success == Add(oldList.EntryAt(index)) condition is always true. To understand why, let's take a closer look at the Add method:

public override FrugalListStoreState Add(T value)
{
  ....
  return FrugalListStoreState.Success;
}

And here's the issue. The method always returns the same value, FrugalListStoreState.Success. Since the if statement is compared against that value, the condition will always be true.

Issue 5

private void NotifyingDataSource_CollectionChanged(
object sender,NotifyCollectionChangedEventArgs e)
{
  ....
  if (ShouldAutoGenerateColumns)
  {
     // The columns are also affected (not just rows) 
    //  in this case so we need to reset everything
    _owner.InitializeElements(false /*recycleRows*/);
  }
  ....
}

The PVS-Studio warning: V3022 Expression 'ShouldAutoGenerateColumns' is always false. DataGridDataConnection.cs 641

And here we go again: we see the same error here. If we examine the ShouldAutoGenerateColumns property, it'll always be false, so the code will never be executed in the if block.

public bool ShouldAutoGenerateColumns
{
  get
  {
    return false;
  }
}

Ghosts of NullReference

The darkest corners of Avalonia hide small invisible spirits—the ghosts of NullReference. They hide in the shadows of uninitialized objects and unexpectedly pop up, interrupting the journey at the worst moment.

Issue 6

private void InitPicker()
{
  ....
  _hourSelector!.MaximumValue = clock12 ? 12 : 23;
  _hourSelector.MinimumValue = clock12 ? 1 : 0;
  _hourSelector.ItemFormat = "%h";
  var hr = Time.Hours;
  _hourSelector.SelectedValue = !clock12 ? hr : hr > 12 ? hr - 12 
                                              : hr == 0 ? 12 : hr;
  ....
  _hourSelector?.Focus(NavigationMethod.Pointer);
}

The PVS-Studio warning: V3095 The '_hourSelector' object was used before it was verified against null. Check lines: 260, 283. TimePickerPresenter.cs 260

The _hourSelector variable appears several times in this fragment. Devs even use the ! operator, showing confidence that the variable can't possibly be null. Only at the end, devs decide to check for null.

Code typos

One wrong character, and the specified path will lead you to impassable swamps instead of the enchanting valley. PVS-Studio analyzer has detected a typical typo in the code. Let's examine this error and see how many times it has appeared.

Issue 7

public ImmutableRadialGradientBrush(RadialGradientBrush source) : base(source)
{
  Center = source.Center;
  GradientOrigin = source.GradientOrigin;
  RadiusX = source.RadiusX;
  RadiusY = source.RadiusX;
}

The PVS-Studio warning: V3056 Consider reviewing the correctness of 'RadiusX' item's usage. ImmutableRadialGradientBrush.cs 74

Due to a typo, devs write the source.RadiusX value to the RadiusY property—and also to the RadiusX property.

It seems that the code logic is violated here. Judging by the code, if we use this brush, the gradient will always be circular and never oval. This code may cause unexpected behavior when using the framework.

Sleeping guardians of the mainland

Tests are the guardians that protect the stability of the mainland. But what happens when they fall asleep on duty and prove to be unreliable? In this section, the analyzer has detected another issue:

Issue 8

[Fact]
public void Content_Can_Be_TopLeft_Aligned()
{
  Border content;
  var target = new ContentPresenter
  {
    Content = content = new Border
    {
      MinWidth = 16,
      MinHeight = 16,
      HorizontalAlignment = HorizontalAlignment.Right,
    },
  };

  target.UpdateChild();
  target.Measure(new Size(100, 100));
  target.Arrange(new Rect(0, 0, 100, 100));

  Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds);
}

[Fact]
public void Content_Can_Be_TopRight_Aligned()
{
  Border content;
  var target = new ContentPresenter
  {
    Content = content = new Border
    {
      MinWidth = 16,
      MinHeight = 16,
      HorizontalAlignment = HorizontalAlignment.Right,
    },
  };

  target.UpdateChild();
  target.Measure(new Size(100, 100));
  target.Arrange(new Rect(0, 0, 100, 100));

  Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds);
}

The PVS-Studio warning: V3013 It is odd that the body of 'Content_Can_Be_TopLeft_Aligned' function is fully equivalent to the body of 'Content_Can_Be_TopRight_Aligned' function. ContentPresenterTests_Layout.cs 169

This code fragment contains two identical test methods. Why use two methods if they test the same thing? Devs might have wanted to check the indents on the left and right, but the tests are identical, so they only check the indents on one side.

Codex of Clean Code: from chaos to order

The heart of Avalonia keeps the tomes inscribed with the Codex of Clean Code, a voluminous set of rules. They help travelers stay on the right path.

In this section, we'll look at the analyzer warning that may lead to incorrect program behavior. To avoid this, devs should support high-quality and clean code.

Issue 9

private static RuleResult LB25(ReadOnlySpan<char> text, LineBreakState state)
{
  switch (state.Next(text).LineBreakClass)
  {
    ....
    // [25.03] NU(SY|IS)* CL × PR
    case LineBreakClass.ClosePunctuation:
    {
      switch (state.Previous.LineBreakClass)
      {
        case LineBreakClass.Numeric:  // <=
        {
          return RuleResult.NoBreak;
        }
        case LineBreakClass.BreakSymbols:
        case LineBreakClass.InfixNumeric:
        {
          if (state.Previous.LineBreakClass == LineBreakClass.Numeric)  // <=
          {
            return RuleResult.NoBreak;
          }

          break;
        }
      }

      break;
    }
    ....
  }
}

The PVS-Studio warning: V3022 Expression 'state.Previous.LineBreakClass == LineBreakClass.Numeric' is always false. LineBreakEnumerator.cs 963

This method contains ten switch-case constructions—some of them are nested up to four times! It's not surprising that it's easy to make mistakes in such a cumbersome code—that's exactly what happened here. Devs attempt to compare the value with LineBreakClass.Numeric twice in one switch block. The first comparison works, but the second one never does because the case has already been matched earlier.

That's a classic among errors: the code just needs to be refactored. You can read about similar cases in another article. We may notice that the switch-case block is a little messy, and the case blocks contain duplicated actions since it's expected that LineBreakClass.Numeric will return RuleResult.NoBreak—just like in the earlier block.

Expedition's final destination

Compared to the last review, the project has made impressive progress. This framework remains popular with users and has become even more reliable and stable over time.

However, some errors detected by PVS-Studio analyzer can lead to unexpected program behavior and impact user experience. I hope this article helps developers spot and fix bugs, which will enhance code quality.

If you're interested in checking your own project, you can try PVS-Studio analyzer.

Posts: articles

Poll:

Subscribe
and get the e-book
for free!

book terrible tips


Comments (0)

Next comments next comments
close comment form
close form

Fill out the form in 2 simple steps below:

Your contact information:

Step 1
Congratulations! This is your promo code!

Desired license type:

Step 2
Team license
Enterprise license
close form
Request our prices
New License
License Renewal
--Select currency--
USD
EUR
close form
Free PVS‑Studio license for Microsoft MVP specialists
close form
To get the licence for your open-source project, please fill out this form
close form
I want to join the test
* By clicking this button you agree to our Privacy Policy statement

close form
check circle
Message submitted.

Your message has been sent. We will email you at


If you do not see the email in your inbox, please check if it is filtered to one of the following folders:

  • Promotion
  • Updates
  • Spam