iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🙌

Trying out Dependency Injection in AvaloniaUI before .NET MAUI Release

に公開

[Introduction] Comparing MAUI and Avalonia

Both are frameworks for building cross-platform applications using C#.

MAUI is scheduled for release around the summer of 2022 (preview versions are already available to try).

Comparison Item MAUI Avalonia
Developer Microsoft OSS
Supported Platforms Android, iOS, macOS, Windows, Linux[1] + WASM[2]

Articles Comparing Both Frameworks

Motivation for Learning Avalonia

Personally, I lean toward choosing MAUI because it is supported by Microsoft.

However, although I haven't been reading the official Avalonia website for long, I have a feeling that XAML can be written quite concisely. Believing that the good points of Avalonia can also be applied to MAUI, I will learn Avalonia before MAUI is released.

In this article, I will try out the Dependency Injection feature.

Why DI?

AvaloniaUI is a XAML-side framework, so starting with DI is technically unusual... but since DI support is essential for resolving (hiding) dependencies effectively, I want to confirm it first.

That concludes the introduction; the next chapter begins the main topic.

Creating an Avalonia Project

Using the Visual Studio extension, I will create an MVVM template project.

mvvm

[Main Topic] Dependency Injection

Avalonia has strong compatibility with ReactiveUI.
If you add using Splat, you can use DI features immediately.

However, while there is documentation on the DI functionality of ReactiveUI itself, there is no mention of how to combine it with Avalonia.
Dependency Injection - ReactiveUI

Specifically regarding registration, I tried using public virtual void RegisterServices(); which the base class of the App class possesses, but there is no guarantee that this is the standard approach.

Registration

Override the method of the App class found in App.xaml.cs.

Prepare a Person class and its interface in a separate Models folder, and register them within this method.
I will register two as named instances and the remaining one as a singleton.

// add references
using Splat;
using AvaloniaApp.Models;

public class App : Application
{
    // override method
    public override void RegisterServices()
    {
        Locator.CurrentMutable.Register<IPerson>(() => new Person("Alice"), "a");
        Locator.CurrentMutable.Register<IPerson>(() => new Person("Bob"), "b");
        Locator.CurrentMutable.RegisterConstant<IPerson>(new Person("Carol"));

        base.RegisterServices();
    }
}

Resolution

For Alice and Bob, an instance is generated every time they are called.
On the other hand, Carol's instance is generated at the time of registration.
Looking at the created at time, it can be seen that Carol was generated at an earlier time.

There is also a method called RegisterLazySingleton, but this type generates an instance when it is called.
You can infer the timing of instance generation based on whether the argument is a delegate or not.

using Splat;

public MainWindowViewModel()
{
    var a = Locator.Current.GetService<IPerson>("a");
    var b = Locator.Current.GetService<IPerson>("b");
    var c = Locator.Current.GetService<IPerson>();

    a.Greeting();
    b.Greeting();
    c.Greeting();
}

/*
I am Alice, created at 20:21:09
I am Bob, created at 20:21:09
I am Carol, created at 20:21:03
*/

Instance Lifetime

  • Instances registered with Register are the same within the generated scope.
  • Instances registered with Constant are the same wherever they are called.
public MainWindowViewModel()
{
    var a = Locator.Current.GetService<IPerson>("a");
    var c = Locator.Current.GetService<IPerson>();

    a.Greeting(); // 1
    c.Greeting(); // 2
    GreetingTemp();
}

private void GreetingTemp()
{
    Thread.Sleep(2000);
    var a = Locator.Current.GetService<IPerson>("a");
    var c = Locator.Current.GetService<IPerson>();
    
    a.Greeting(); // 3
    c.Greeting(); // 4
}

/*
I am Alice, created at 3:48:41 
I am Carol, created at 3:48:39 
I am Alice, created at 3:48:43
I am Carol, created at 3:48:39
*/

Notes on Registration

Since the instance registered later takes precedence, in the case below, there is no way to call Carol.

// Registration
Locator.CurrentMutable.RegisterConstant<IPerson>(new Person("Carol"));
Locator.CurrentMutable.RegisterConstant<IPerson>(new Person("CarolBrother"));                   

// Resolution
var c = Locator.Current.GetService<IPerson>(); // <- CarolBrother

Conclusion

Based on the Avalonia MVVM template, I tried out Dependency Injection.

Overriding the method inherited by App seems to provide a clean fit.

  • Since it is a function without arguments, it does not strictly have to be put into override RegisterServices.
脚注
  1. Platforms supported by MAUI - Additional platform support ↩︎

  2. GitHub Issue "Roadmap" - AvaloniaUI/Avalonia ↩︎

Discussion