头图

We're excited to announce the official availability of the new .NET Community Toolkit, now available on NuGet as version 8.0.0! This is a major release that includes tons of new features, improvements, optimizations, bug fixes, and many refactorings that reflect the new project structure and organization, which this blog post will describe in detail.
图片
As with every Community Toolkit release, all changes are influenced by feedback from Microsoft Teams and other developers in the community who use the Toolkit. We are so grateful to everyone who has contributed and continues to help make the .NET Community Toolkit even better!

What's in the .NET Community Toolkit?

The .NET Community Toolkit is a set of helpers and APIs for all .NET developers, independent of any particular UI platform. The toolkit is maintained and published by Microsoft and is part of the .NET Foundation. It's also used by some internal projects and inbox apps, such as the Microsoft Store. As of the new 8.0.0 release, the project is now in the CommunityToolkit/dotnet repository on GitHub, which includes all the libraries that are part of the Toolkit.

All available APIs are not tied to any particular runtime or framework, so all .NET developers can use them. These libraries are multi-targeting libraries from .NET Standard 2.0 to .NET 6, so they can both support as many platforms as possible and be optimized for best performance when used with newer runtimes.

Libraries in the .NET Community Toolkit include:
CommunityToolkit.Common
CommunityToolkit.Mvvm (aka "Microsoft MVVM Toolkit")
CommunityToolkit.Diagnostics
CommunityToolkit.HighPerformance

Community Toolkit History at a Glance

You might be wondering why the first version of the .NET Community Toolkit was version 8.0.0. good question! The reason is that all of the .NET Community Toolkit's libraries were originally part of the Windows Community Toolkit, which is a collection of helpers, extensions, and custom controls, and custom controls that simplify and demonstrate building UWP for Windows 10 and Windows 11 and common developer tasks for .NET applications.
Over time, the number of APIs that only target .NET and don't have any Windows specific dependencies has grown, and we decided to split them into a separate project so they can evolve independently, and for APIs that don't do any Windows development It's also easier to find for .NET developers. That's how the .NET Community Toolkit was born. This also makes it easier and better to organize our documentation, now each platform-specific toolkit has its own individual documentation .
Since the last version of the Windows Community Toolkit before the fork was 7.1.x, we decided to follow that semantic version number to make the transition easier for existing users to understand, and this is how the first version of the .NET Community Toolkit was 8.0.0 The reason. Going forward, it will be versioned separately from the Windows Community Toolkit , as each project has its own independent roadmap and release schedule.
With that out of the way, now let's dive into all the new features in this new major release of the .NET Community Toolkit library!

MVVM Toolkit

As previously announced in the 7.0 release , one of the main components of the .NET Community Toolkit is the MVVM Toolkit: a modern, fast, platform-independent and modular MVVM library. This is the same MVVM library used by the Microsoft Store, Photos app, and more!

The MVVM toolkit was inspired by MvvmLight , and since this library has been deprecated, the MVVM toolkit is also the official replacement for MvvmLight. We are also working with Laurent Bugnion while developing the MVVM toolkit, who supports the MVVM toolkit as an upgrade path for existing MvvmLight users (we also have migration documentation for this).

The MVVM toolkit is built on several key principles:

  • Platform independent: means it is not tied to a specific UI framework. You can use it to share code between UWP, WinUI 3, MAUI, WPF, Avalonia, Uno, and more!
  • Runtime agnostic: The library supports multi-targeting and supports environments down to .NET Standard 2.0, which means you can get performance improvements when running on modern runtimes (such as .NET 6) and still can use it.
  • Easy to pick up and use: There are no strict requirements on the application structure or coding patterns used. You can use this library to adapt to your own architecture and style.
  • À la carte: All components are self-contained and can also be used individually. There's no way of forcing you to use "all": if you only want to use one type from the whole library, you can do it well, and then gradually start using more functions as needed.
  • Reference Implementations: All available APIs are lean and performant, providing "reference implementations" for the interfaces contained in the .NET base class library, but lacking concrete types to use them directly. For example, you will be able to find "reference implementations" of interfaces like INotifyPropertyChanged or ICommand.

MVVM Toolkit Source Generator

The biggest new feature in version 8.0.0 of the MVVM Toolkit is the new MVVM source code generator , which is designed to greatly reduce the boilerplate code required to set up an application with MVVM. Compared to the preview generators we released in 7.1.0 , they've also been completely rewritten as incremental generators , which means they'll run faster than before, and will have more power even when working on large projects Helps keep the IDE responsive.

You can find all the documentation on the new source generators here, and if you prefer the video version, James Montemagno has also made several videos about them. Let's also review the main features powered by source generators, which you can find in the MVVM toolkit.

Order

Creating commands can be quite repetitive, and with the requirement to set a property for each method, we want to expose these methods in an abstract way to the various UI components in the application that call them (like buttons).
This is where the new [RelayCommand] attribute comes into play: this will cause the MVVM toolkit to automatically generate a command with the correct signature (using the RelayCommand type included in the library), depending on the annotated method.
For comparison, here is the code for how people would typically set up a command in the past:

 private IRelayCommand<User> greetUserCommand;

public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);

private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}

Now it can be simplified to:

 [RelayCommand]
private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}

The source generator will take care of creating the correct GreetUserCommand property based on the annotated method. Additionally, you can specify the CanExecute method, and you can control the level of concurrency for asynchronous commands. There are other options to fine-tune the behavior of the generated command, you can learn more about it in our documentation .

observable property

Writing observable properties can be very verbose, especially when you also have to add extra logic to handle notified dependency properties. All of this can now be greatly simplified by using the new properties in the MVVM toolkit and letting the source generator create the observable properties behind the scenes.

These new properties are [ObservableProperty], [NotifyPropertyChangedFor] and [NotifyCanExecuteChangedFor], [NotifyDataErrorInfo] and [NotifyPropertyChangedRecipients]. Let's quickly review what all these new properties can do. Consider a scenario where there are two observable properties, a dependency property and the command defined above, when either of the two observable properties changes, both the dependency property and the command need to be notified. That is, whenever FirstName or LastName changes, FullName and GreetUserCommand are also notified.

This is how it used to be:

 private string? firstName;

public string? FirstName
{
    get => firstName;
    set
    {
        if (SetProperty(ref firstName, value))
        {
            OnPropertyChanged(nameof(FullName));
            GreetUserCommand.NotifyCanExecuteChanged();
        }
    }
}

private string? lastName;

public string? LastName
{
    get => lastName;
    set
    {
        if (SetProperty(ref lastName, value))
        {
            OnPropertyChanged(nameof(FullName));
            GreetUserCommand.NotifyCanExecuteChanged();
        }
    }
}

public string? FullName => $"{FirstName} {LastName}";

Now it can all be rewritten as follows:

 [ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private string? firstName;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private string? lastName;

public string? FullName => $"{FirstName} {LastName}";

The MVVM toolkit will handle the code generation for these properties, including inserting all the logic to raise the specified property change or execute the change event. But wait, there are more features! When using [ObservableProperty] to generate observable properties, the MVVM toolkit will now also generate two partial methods that are not implemented: On<PROPERTY_NAME>Changing and On<PROPERTY_NAME>Changed . These methods can be used to inject additional logic when changing properties without falling back to using manual properties. Note that since these two methods are partial, return void, and are not defined, the C# compiler will remove them entirely if they are not implemented, which means they will disappear when not in use and will not be added to the middle of the application.
Here is an example of how to use them:

 [ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private string? firstName;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private string? lastName;

public string? FullName => $"{FirstName} {LastName}";

Of course, you can also use only one of these two methods, or none of them. From the code snippet above, the source generator will generate code similar to the following:

 [ObservableProperty]
private string name;

partial void OnNameChanging(string name)
{
    Console.WriteLine($"The name is about to change to {name}!");
}

partial void OnNameChanged(string name)
{
    Console.WriteLine($"The name just changed to {name}!");
}

The [ObservableProperty] attribute also supports validation: if any field representing a property has one or more properties that inherit from ValidationAttribute, these are automatically copied into the resulting property, so this is also fully supported when creating a validable property using the ObservableValidator a method. You can also add [NotifyDataErrorInfo] to generate validation code in the property setter if you also want the property to be validated when its value is set. [ObservableProperty] There are many more functions available, just like commands, you can read more about them and see more examples in our documentation .

Remove support for commands

A new property has been added to the [RelayCommand] property that can be used to instruct the source generator to generate a cancel command alongside the original command. This cancel command can be used to cancel the execution of an asynchronous command. This also shows how [RelayCommand] automatically adapts to async methods and methods that accept parameters, and creates an implementation of the async command behind the scenes. This also enables other features like easy to setup bindings to show progress indicators and more!

Here is an example of how to use them:

 [RelayCommand(IncludeCancelCommand = true)]private async Task DoWorkAsync(CancellationToken token){    // 使用取消支持做一些长期运行的工作}

From this small snippet, the generator will generate the following code:

 private AsyncRelayCommand? doWorkCommand;public IAsyncRelayCommand DoWorkCommand => doWorkCommand ??= new AsyncRelayCommand(DoWorkAsync);ICommand? doWorkCancelCommand;public ICommand DoWorkCancelCommand => doWorkCancelCommand ??= IAsyncRelayCommandExtensions.CreateCancelCommand(UpdateSomethingCommand);

The generated code, combined with the logic in the IAsyncRelayCommandExtensions.CreateCancelCommand API, lets you generate a command with just one line of code, notify the UI when a job starts or runs, and has automatic concurrency control (the command is the default when it is already running) disabled). Whenever the main command starts or ends running, a separate cancel command will be notified and, upon execution, will signal the cancellation to the token passed to the method wrapped by the main command. All of this, completely abstracted and easily accessible with just one property.

Change support for Broadcast of generated properties

We've also added a new [NotifyPropertyChangedRecipients] property that can be used for observable properties generated from ObservableRecipient (or types annotated with [ObservableRecipient] ) that inherit from it. Use it to generate a call to the Broadcast method to send a message to all other subscribing components about the property change that just happened. This is useful in cases where the view model's property changes also need to notify other components in the application (assuming there is an IsLoggedIn boolean property that updates when the user is logged in; this can notify and trigger the application to refresh the Broadcast message).

It can be used as follows:

 [ObservableProperty][NotifyPropertyChangedRecipients]
private string name;

This will produce code similar to this:

 public string Name{    get => name;    set    {        if (!EqualityComparer<string>.Default.Equals(name, value))        {            OnNameChanging(value);            
OnPropertyChanging();            
string oldValue = name;            
name = value;            
Broadcast(oldValue, value, nameof(Name));            
OnNameChanged();            
OnPropertyChanged();
        }
    }}

This is another feature that enhances generated properties and ensures they can be used in almost all scenarios without being forced to fall back to manual properties.

ViewModel composition

C# doesn't have multiple inheritance, which can sometimes be a hindrance. What if you have a view model that must inherit from a specific type, but you also want to inject INotifyPropertyChanged support into it, or have it also inherit from ObservableRecipient to access its API? The MVVM toolkit now addresses this problem by introducing code generation properties that allow these types of logic to be injected into arbitrary classes. They are [INotifyPropertyChanged], [ObservableObject], and [ObservableRecipient]. Adding them to a class will cause the MVVM Toolkit source code generator to include all the logic for that type into the class as if the class also inherited from that type. For example: [INotifyPropertyChanged]partial class MyObservableViewModel : DatabaseItem{} This MyObservableViewModel will inherit from DatabaseItem as you would expect, but using [INotifyPropertyChanged] will make it also support INotifyPropertyChanged, along with all the helper APIs that ObservableObject itself contains.

We still recommend inheriting from base types (such as ObservableObject) when needed, as this also helps reduce binary size, but the ability to inject code this way when needed can help in situations where changing the base type of the view model is not possible Work around the limitations of C#, like the example above.

Improved Messenger API

Another commonly used feature in the MVVM toolkit is the IMessenger interface, which is a type contract that can be used to exchange messages between different objects. This is useful for decoupling different modules of an application without having to keep strong references to reference types. It is also possible to send messages to specific channels, uniquely identified by tokens, and have different messengers in different parts of the application. The MVVM toolkit provides two implementations of this interface:
WeakReferenceMessenger: It does not pin recipients and allows to collect them. This is achieved through dependency handles, a special type of GC reference that allows this courier to ensure that registered receivers are always allowed to be collected, even if a registered handler references them back, but no other reference to them exists Unfinished strong reference.
StrongReferenceMessenger: This is a messenger implementation that root-authorizes registered receivers to ensure they remain active even if the messenger is the only object referencing them. Here is a small example of how to use this interface:

 // 声明消息
public sealed record LoggedInUserChangedMessage(User user);
// 明确注册收件人...
messenger.Register<MyViewModel, LoggedInUserChangedMessage>(this, static (r, m) =>
{
// 在这里处理消息,r 是接收者,m 是接收者
// 输入消息。使用作为输入传递的接收者使得    
// ambda 表达式不捕获“this”,从而提高性能。
});

// ... 或者让视图模型实现 IRecipient<TMessage>......
class MyViewModel : IRecipient<LoggedInUserChangedMessage>
{
    public void Receive(LoggedInUserChangedMessage message)
    {
        // 在这里处理消息
    }
}

// ... 然后通过接口注册(其他API也可用)messenger.Register<LoggedInuserChangedMessage>(this);

// 从其他模块发送消息
messenger.Send(new LoggedInUserChangedMessage(user));

The messenger implementation in this new version of the MVVM toolkit is highly optimized in .NET 6 thanks to the newly provided public DependentHandle API , which allows the messenger type to become faster than before and provides completely zero-allocation message broadcasting. Here are some benchmarks showing how the messenger in the MVVM toolkit compares to several other equivalent types in other widely used MVVM libraries:

image.png
image.png

Each benchmark run involved sending 4 different messages 1000 times to 100 recipients. As you can see, both WeakReferenceMessenger and StrongReferenceMessenger are by far the fastest and the only ones that don't even allocate a single byte when broadcasting a message.

Improved Collections API

This new version of the MVVM toolkit also moves all observable grouping collection types from the CommunityToolkit.Common package to CommunityToolkit.Mvvm, while also making some major changes to improve the API surface and make it useful in more scenarios. These APIs are particularly useful when dealing with grouped items (for example, displaying a list of contacts), and they now also include extensions to greatly facilitate common operations such as inserting items at the correct location within a group (using the default comparator or entering one and placing one in create a new group if needed).

Here's a GIF showing a simple Contacts view from the MVVM Toolkit sample app:
图片

Announcing the MVVM Toolkit sample application

To accompany the new release, we've also released a sample app in the Microsoft Store! It includes all the documentation that can be found on MS Docs, as well as interactive examples of many of the available APIs. It's intended to be a companion to the MVVM toolkit, and we hope it helps people get started with the library and become more familiar with it! Download and try it from the Microsoft Store !
图片

Improved diagnostic API

The CommunityToolkit.Diagnostics package has also received some new improvements, taking advantage of the new C# 10 interpolated string handler and caller parameter expression features . Some Guard APIs that previously accepted strings now also accept custom handlers, allowing the call site to skip the interpolation step entirely without throwing an exception, and also no longer need to manually indicate parameter names. Here's a quick before and after comparison:

 // 诊断 7.1
public static void SampleMethod(int[] array, int index, Span<int> span, string text)
{
    Guard.IsNotNull(array, nameof(array));
    Guard.HasSizeGreaterThanOrEqualTo(array, 10, nameof(array));
    Guard.IsInRangeFor(index, array, nameof(index));
    Guard.HasSizeLessThanOrEqualTo(array, span, nameof(span));
    Guard.IsNotNullOrEmpty(text, nameof(text));
}
 // 诊断 8.0
public static void SampleMethod(int[] array, int index, Span<int> span, string text)
{
    Guard.IsNotNull(array);
    Guard.HasSizeGreaterThanOrEqualTo(array, 10);
    Guard.IsInRangeFor(index, array);
    Guard.HasSizeLessThanOrEqualTo(array, span);
    Guard.IsNotNullOrEmpty(text);
}

.NET 6 support

This new version of the .NET Community Toolkit also adds support for .NET 6 as a new target for all available libraries. When running on the latest .NET runtime, it brings some improvements:

  • Trim support is now enabled for all libraries. To support this, all packages also provide full trim comments for all APIs to ensure that everything is either linker friendly or explicitly displays the correct warnings at compile time (for example, some validation APIs in the MVVM toolkit That's the case, they use some APIs in the BCL that inherently require some reflection to work).
  • The Count<T>() extension in the High Performance package now also supports mint and nunit.
  • Several other optimizations have been introduced for all packages on .NET 6.
    Of course, all libraries will continue to support .NET Standard 2.0, so you can continue to reference them from projects with different target frameworks as well. Because of how NuGet package resolution works, if you write a library using these packages and a lower target framework (eg. .NET Standard 2.0), and the consumer references it from a project targeting a newer .NET version (eg. .NET 6), They still automatically get the most optimized versions of the .NET Community Toolkit assemblies available!

There's more to come in this new release! You can view the full changelog on the GitHub releases page.

You can find all source code on our GitHub repository , some handwritten documentation on the MS Docs website , and the full API reference on the .NET API Browser website. If you'd like to contribute, feel free to ask a question or contact us and let us know about your experience! To follow the conversation on Twitter, use the hashtag #CommunityToolkit. All of your feedback has greatly helped the direction of these libraries, so be sure to share them!

图片
Long press to identify the QR code and follow Microsoft Developer MSDN

Click to learn about the .NET Community Toolkit~


微软技术栈
423 声望998 粉丝

微软技术生态官方平台。予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。