GithubHelp home page GithubHelp logo

communitytoolkit / dotnet Goto Github PK

View Code? Open in Web Editor NEW
2.7K 2.7K 256.0 4.25 MB

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.

Home Page: https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin

License: Other

C# 99.97% PowerShell 0.03%
csharp dotnet maui mvvm netcore netframework netstandard uno-platform uwp wpf

dotnet's Introduction

🧰 .NET Community Toolkit

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.

πŸ‘€ What does this repo contain?

This repository contains several .NET libraries (originally developed as part of the Windows Community Toolkit) that can be used both by application developers (regardless on the specific UI framework in use, they work everywhere!) and library authors. These libraries are also being used internally at Microsoft to power many of our first party apps (such as the new Microsoft Store) and constantly improved by listening to feedbacks from other teams, external partners and other developers from the community. Here's a quick breakdown of the various components you'll find in this repository:

Package Latest stable Latest Preview Description
CommunityToolkit.Common CommunityToolkit.Common CommunityToolkit.Common A set of helper APIs shared with other CommunityToolkit libraries.
CommunityToolkit.Diagnostics CommunityToolkit.Diagnostics CommunityToolkit.Diagnostics A set of helper APIs (specifically, Guard and ThrowHelper) that can be used for cleaner, more efficient and less error-prone argument validation and error checking.
CommunityToolkit.HighPerformance CommunityToolkit.HighPerformance CommunityToolkit.HighPerformance A collection of helpers for working in high-performance scenarios. It includes APIs such as pooled buffer helpers, a fast string pool type, a 2D variant of Memory<T> and Span<T> (Memory2D<T> and Span2D<T>) also supporting discontiguous regions, helpers for bit shift operations (such as BitHelper, also used in Paint.NET), and more.
CommunityToolkit.Mvvm (aka MVVM Toolkit) CommunityToolkit.Mvvm CommunityToolkit.Mvvm A fast, modular, platform-agnostic MVVM library, which is the official successor of MvvmLight. It's used extensively in the Microsoft Store and other first party apps. The sample app repository is here.

πŸ™Œ Getting Started

Please read the Getting Started with the .NET Community Toolkit page for more detailed information.

πŸ“ƒ Documentation

All documentation for the toolkit is hosted on Microsoft Docs.

All API documentation can be found at the .NET API Browser.

πŸš€ Contribution

Do you want to contribute?

Check out our .NET Community Toolkit Wiki page to learn more about contribution and guidelines!

πŸ“¦ NuGet Packages

NuGet is a standard package manager for .NET applications which is built into Visual Studio. When you open solution in Visual Studio, choose the Tools menu > NuGet Package Manager > Manage NuGet packages for solution… Enter one of the package names mentioned in .NET Community Toolkit NuGet Packages table to search for it online.

🌍 Roadmap

Read what we plan for next iterations, and feel free to ask questions.

Check out our Preview Packages Wiki Page to learn more about updating your NuGet sources in Visual Studio, then you can also get pre-release packages of upcoming versions to try.

πŸ“„ Code of Conduct

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information see the .NET Foundation Code of Conduct.

🏒 .NET Foundation

This project is supported by the .NET Foundation.

πŸ† Contributors

Toolkit Contributors

Made with contrib.rocks.

dotnet's People

Contributors

avknaidu avatar azchohfi avatar code-scottle avatar danielchalmers avatar dazombiekiller avatar h82258652 avatar hansmbakker avatar heku avatar herrickspencer avatar john-h-k avatar kyaa-dost avatar matthew4850 avatar michael-hawker avatar nirmal4g avatar nmetulev avatar orondf343 avatar ovska avatar ratishphilip avatar robloo avatar rosuavio avatar sergio0694 avatar shweaver-msft avatar simoncropp avatar steinhilber avatar validvoid avatar vgromfeld avatar williamabradley avatar xamlbrewer avatar youssef1313 avatar zhuman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dotnet's Issues

[Feature] MVVM Toolkit: allow async reception of messages

Describe the problem this feature would solve

I have started replacing MvvmLight and Prism with the new MVVM Toolkit and right now I'm loving it. However, I had to use the messaging infrastructure for the first time today and found a small limitation - the default way of processing messages is always run in a synchronous way. I think it would be useful to give the option to process them in an asynchronous way if desired.

Describe the solution

A very basic implementation would look like this:

public interface IAsyncRecipient<in TMessage> where TMessage : class
{
    Task ReceiveAsync(TMessage message);
}

Describe alternatives you've considered

Right now I'm manually scheduling a task in IRecipient.Receive to process messages that need calling async functions, but feels a bit wrong to do it this way.

Additional context & Screenshots

None.

Guard: Constrained generic type to class/struct prevent any unconstraint use.

Overview

Being able to use struct instead of class for performance is more and more common. Hoewever, the Guard.IsNotNull cannot be used for such case. However, removing the constraints gives the exact same results.

Below is a concrete example:

/// <summary>
/// Adapts a comparer and a value to a comparable.
/// To be used with <see cref="MemoryExtensions"/> binary search span extension methods.
/// </summary>
public readonly struct ComparerComparable<T, TComparer> : IComparable<T>
    where TComparer : IComparer<T>
{
    private readonly T _value;
    private readonly TComparer _comparer;

    public ComparerComparable( T value, TComparer comparer )
    {
        Guard.IsNotNull( value, nameof( value ) );
        Guard.IsNotNull( comparer, nameof( comparer ) );
        _value = value;
        _comparer = comparer;
    }

    [MethodImpl( MethodImplOptions.AggressiveInlining )]
    public int CompareTo( T? other ) => _comparer.Compare( _value, other );
}

This doesn't compile.
CS0452 The type 'T' must be a reference type in order to use it as parameter 'T' in the generic type or method 'Guard.IsNotNull(T?, string)'

Expected behavior

We should be able to use IsNotNull (and others) on unconstrained types.

Environment

NuGet Package(s): 7.1.2 (latest)

Additional context

I tested my hypothesis here: https://sharplab.io/#gist:32b79a5f6d137a29671f7a72c6083922 to be sure...
Basically, absolutely nothing changed, except that the following stupid code can be written:

    // => 3 bytes. Stupid code leads to 0 overhead.
    public int GuardNCVal( int x ) {
        IsNotNullNC(x, nameof(x) );
        return x.GetHashCode();
    }

As the comment says, this is a no-op (check this on the SharpLab gist).

I will happily provide a PR for this if the idea is accepted (and there is no suble issues I missed).

MissingMethodException with netstandard2.0 target

Describe the bug

When a netstandard2.0 library with a dependency on Microsoft.Toolkit.HighPerformance uses certain extension methods such as StreamExtensions.Write(Stream,ReadOnlySpan<Byte>), it can lead to unexpected runtime failures with a System.MissingMethodException when run on a target that has SPAN_RUNTIME_SUPPORT.

Example: Class library multitargets net6.0 and netstandard2.0:

using Microsoft.Toolkit.HighPerformance;

//problem call:
Span<byte> header;
Stream output;
output.Write(header);

This call will compile successfully, with the net6.0 target using the native Stream.Write overload and the netstandard2.0 target using the WCT extension.

However, when this library is run on a platform that has SPAN_RUNTIME_SUPPORT defined, e.g., netcoreapp3.1 or net5.0, it will fail at runtime with the following exception:

System.MissingMethodException : Method not found: 'Void Microsoft.Toolkit.HighPerformance.StreamExtensions.Write(System.IO.Stream, System.ReadOnlySpan`1<Byte>)

This appears to be due to the preprocessor directive that only generates the StreamExtensions.Write(Stream,ReadOnlySpan<Byte>) when SPAN_RUNTIME_SUPPORT is false.

https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/da6d7d3f6ca9914dbe86d7d394e9a4abef25c9b6/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs#L22

Regression

No response

Reproducible in sample app?

  • This bug can be reproduced in the sample app.

Steps to reproduce

1. In a `netstandard2.0` class library, use a WCT extension method that is dependent on the `SPAN_RUNTIME_SUPPORT` directive
2. Run the class library on a pre-`net6.0` target that includes `SPAN_RUNTIME_SUPPORT`
3. Observe `MissingMethodException`

Expected behavior

Runtime light-up instead of silent failure: forward calls to the underlying Stream.Write implementation when SPAN_RUNTIME_SUPPORT is present.

Nuget packages

  • Microsoft.Toolkit.HighPerformance version 7.1.2

Help us help you

Yes, I'd like to be assigned to work on this item.

NullableExtensions.DangerousGetValueOrNullReference()

The existing DangerousGetValueOrDefaultReference() is useful, but for a lot of my interop code I need null when HasValue is false

I'm actually using a more dangerous version in my code base that returns a pointer, but @Sergio0694 says return a null ref is a better idea, and I agree. Can always Unsafe.AsPointer() from that.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T DangerousGetValueOrNullReference<T>(ref this T? maybe)
    where T : unmanaged
{
    return ref (maybe.HasValue ? ref maybe.DangerousGetValueOrDefaultReference() : ref Unsafe.NullRef<T>());
}

[Feature] APIs to one-time subscribe to/await events

Follow up from a request by @michael-hawker on his latest Twitch stream.

Describe the problem this feature would solve

This issue is about having some APIs to allow users to subscribe to events just once, or to asynchronously await for an event to be raised, without having to manually define the handler, register/unregister the event from within the handler, and setup Task-s to await if needed.

Note: that this issue is mostly just to gather feedbacks and discuss the idea at this point, I'm not actually opening a PR to add this feature. This is just to have a reference after the previous conversation.

Describe the solution

I came up with a simple class that adds 3 methods that can be used to solve this issue. The signature can arguably seem a bit weird, but I don't think there's another way to do this at least at the moment, especially on UWP. Code generators might come in handy in the future, but those are still a ways off:

public static class EventAwaiter
{
    public static async Task Await<THandler>(
        Action<THandler> register,
        Action<THandler> unregister,
        THandler handler)
        where THandler : Delegate;

    public static async Task Await<THandler>(
        Action<THandler> register,
        Action<THandler> unregister)
        where THandler : Delegate;

    public static async Task<TArgs> Await<THandler, TArgs>(
        Action<THandler> register,
        Action<THandler> unregister)
        where THandler : Delegate
        where TArgs : class;
}

The full code can be found in my gist here.

The way these work is by creating a TaskCompletionSource<T> behind the scenes, then using LINQ expression trees to wire up a custom handler that sets that task source, registering the handler, awaiting the task and then unregistering the handler. This is all completely transparent to the user, which just sees a nice API that supports the TPL workflow. Here's a sample usage:

var button = new Button();

// BEFORE
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();

void Handler(object s, EventArgs e)
{
    tcs.TrySetResult(null);
    button.Loaded -= Handler;
}

button.Loaded += Handler;

await tcs.Task;

// AFTER
await EventAwaiter.Await<RoutedEventHandler>(
    h => button.Loaded += h,
    h => button.Loaded -= h);

Those two lambdas to register/unregister events are needed right now as C# doesn't have proper support for registering events externally (this is by design, as far as I know).

Describe alternatives you've considered

One possible alternative would be to leverage the APIs in the System.Reactive package, though that would require a whole new dependency just for this, and would still result in a very similar API surface for users anyway. Also that would probably involve even more overhead. With the proposed solution instead the whole thing is 100% self-contained and without external dependencies.

MVVM Source Generators: CS1028 Error if #region block

Compilation fails for the Microsoft MVVM Toolkit source generator and a #region block.
(error CS1028: Unexpected preprocessor directive)

Describe the bug

A clear and concise description of what the bug is.

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:

Steps to Reproduce

namespace App3
{
    #region Region
    public class Region
    {
    }
    #endregion

    public partial class ViewModel
    {
        [ICommand]
        private void Greet() { }
    }
}

Environment

NuGet Package(s): Microsoft.Toolkit.Mvvm
Package Version(s): 7.1.1

Windows 10 Build Number:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

App min and target version:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

Device form factor:

  • Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2017 (15.{minor_version})
  • 2019 (16.{minor_version})
  • 2022 (17.{minor_version})

Additional context

(Duplicate of CommunityToolkit/WindowsCommunityToolkit#4225 ?)

Can't easily Map collection from .NET Standard to MetadataControl?

Describe the bug

Since we've been doing a lot more with the MVVM Toolkit lately, I was thinking about the scenario where your data model may be coming from .NET Standard. E.g. having a list of items for tags or something.

Currently, you'd have to hard create a new list of MetadataItem from that list and make sure you observe any changes to re-map yourself.

It'd be nice if we could take in any collection of items and map to the properties we need instead. Like ComboBox does with DisplayMemberPath for instance. Though this may be a bit odd for commands?

It's also a lot more reflection heavy to do it that way... And I don't think we'd want a general MetadataItem type class in our .NET Standard libraries to be tied specifically to a control in our UI library...

Do we see this as an issue with using the control?

Environment

NuGet Package(s): 7.1.0-rc1

Package Version(s):

Windows 10 Build Number:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

App min and target version:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

Device form factor:

  • Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2017 (15.{minor_version})
  • 2019 (16.{minor_version})
  • 2022 (17.{minor_version})

Additional context

FYI @vgromfeld @Sergio0694

Switch MVVM Toolkit generators to incremental generators

Contributes to #8

Now that VS2022 is officially out, we should migrate all existing source generators in the MVVM Toolkit to be incremental generators. This will bring a major performance improvement to the IDE responsiveness and typing experience, as the current approach using non-incremental generators based on SyntaxReceiver-s has known scalability issues with large solutions.

[Feature] MVVM Toolkit vNext: source generators! πŸš€

The Microsoft.Toolkit.Mvvm package (aka MVVM Toolkit) is finally out, so it's time to start planning for the next update πŸ˜„
This issue is meant to be for tracking planned features, discuss them and possibly propose new ideas well (just like in CommunityToolkit/WindowsCommunityToolkit#3428).

One aspect I want to investigate for this next release, which I think makes sense for the package, is support for Roslyn source generators. Support for these would be added through a secondary project, ie. Microsoft.Toolkit.Mvvm.SourceGenerators, that would be shipped together with the MVVM Toolkit, but then added to consuming projects as a development dependency, ie. as an analyzer. They would be included in the same NuGet package, so it wouldn't be possible to just reference the source generator directly (which wouldn't make sense anyway). This is the same setup I'm using in my own library ComputeSharp, in fact I'm applying a lot of the things I learnt while working on that project to the MVVM Toolkit as well πŸ₯³

There are two three main aspects that can be investigated through support for source generators:

  • Better extensibility and composition (opt-in)
  • Less verbosity (opt-in)
  • Better performance (always on)

Here's some more details about what I mean by these two categories exactly.

Better extensibility

There's one "problem" with C# that has been brought up by devs before while using the MVVM Toolkit and in general (eg. here): lack for multiple inheritance. While that makes perfect sense and there's plenty of valid reasons why that's the case, the fact remains that it makes things sometimes inconvenient, when eg. you want/need to inherit from a specific type but then still want to use the features exposed by other classes in the MVVM Toolkit. The solution? Source generators! πŸš€

For now I'm thinking about adding the following attributes to the package:

  • [INotifyPropertyChanged]
  • [ObservableObject]
  • [ObservableRecipient]

The way they work is that they let you annotate a type and then rely on the generator injecting all the APIs from those types automatically, so you don't need to worry about it and it's like you were effectively having multiple inheritance. Here's an example:

[ObservableObject]
partial class MyViewModel : SomeOtherClass
{
}

This class now inherits from SomeOtherClass, but it still has all the same APIs from ObservableObject! This includes the PropertyChanged and PropertyChanging events, the methods to raise them and all the additional helper methods!

[ObservableRecipient] does the same but copying members from ObservableRecipient (eg. this could be used to effectively have a type that is both a validator but also a recipient), and [INotifyPropertyChanged] instead offers minimal support just for INotifyPropertyChanged, with optionally also the ability to include additional helpers or not. This approach and the different attributes offer maximum flexibility for users to choose the best way to construct their architecture without having to compromise between what APIs to use from the MVVM Toolkit and how they want/have to organize their type hierarchy. πŸŽ‰

NOTE: this category is marked as "opt-in" because the attributes are completely optional. Not using them will have no changes at all on the behavior of the toolkit, so developers just wanting to inherit from the base types in the library as usual will absolutely still be able to do so. This just gives consumers more flexibility depending on their exact use case scenario.

Less verbosity

This was first suggested by @michael-hawker in this comment, the idea is to also provide helpers to reduce the code verbosity in simple cases, such as when defining classic observable properties. For now I've added these attributes:

  • [ObservableProperty]
  • [AlsoNotifyFor]
  • [ICommand]

The first two can be used to easily declare observable properties, by annotating a field. [ObservableProperty] will create the code necessary to implement the property itself, whereas [AlsoNotifyFor] will customize the generated code by adding extra notification events (ie. calls to OnPropertyChanged) for properties whose value depends on the property being updated.

Here's an example of how these two attributes can be used together:

Viewmodel definition:
public sealed partial class PersonViewModel : ObservableObject
{
    [ObservableProperty]
    [AlsoNotifyFor(nameof(FullName))]
    private string name;

    [ObservableProperty]
    [AlsoNotifyFor(nameof(FullName))]
    private string surname;

    public string FullName => $"{Name} {Surname}";
}
Generated code:
public sealed partial class PersonViewModel
{
    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "7.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public string Name
    {
        get => name;
        set
        {
            if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
            {
                OnPropertyChanging();
                name = value;
                OnPropertyChanged();
                OnPropertyChanged("FullName");
            }
        }
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "7.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public string Surname
    {
        get => surname;
        set
        {
            if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(surname, value))
            {
                OnPropertyChanging();
                surname = value;
                OnPropertyChanged();
                OnPropertyChanged("FullName");
            }
        }
    }
}

There is also a brand new [ICommand] attribute type, which can be used to easily create command properties over methods, by leveraging the relay command types in the MVVM Toolkit. The way this works is simple: just write a method with a valid signature for either RelayCommand, RelayCommand<T>, AsyncRelayCommand or AsyncRelayCommand<T> and add the [ICommand] attribute over it - the generator will create a lazily initialized property with the right command type that will automatically wrap that method. Cancellation tokens for asynchronous commands are supported too! πŸš€

Here's an example of how this attribute can be used with four different command types:

Viewmodel definition:
public sealed partial class MyViewModel
{
    [ICommand]
    private void Greet()
    {
    }

    [ICommand]
    private void GreetUser(User user)
    {
    }

    [ICommand]
    private Task SaveFileAsync()
    {
        return Task.CompletedTask;
    }

    [ICommand]
    private Task LogUserAsync(User user)
    {
        return Task.CompletedTask;
    }
}
Generated code:
public sealed partial class MyViewModel
{
    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    private global::Microsoft.Toolkit.Mvvm.Input.RelayCommand? greetCommand;

    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public global::Microsoft.Toolkit.Mvvm.Input.IRelayCommand GreetCommand => greetCommand ??= new global::Microsoft.Toolkit.Mvvm.Input.RelayCommand(new global::System.Action(Greet));

    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    private global::Microsoft.Toolkit.Mvvm.Input.RelayCommand<global::MyApp.User>? greetUserCommand;

    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public global::Microsoft.Toolkit.Mvvm.Input.IRelayCommand<global::MyApp.User> GreetUserCommand => greetUserCommand ??= new global::Microsoft.Toolkit.Mvvm.Input.RelayCommand<global::UnitTests.Mvvm.Test_ICommandAttribute.User>(new global::System.Action<global::UnitTests.Mvvm.Test_ICommandAttribute.User>(GreetUser));
    
    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    private global::Microsoft.Toolkit.Mvvm.Input.AsyncRelayCommand? saveFileAsyncCommand;

    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public global::Microsoft.Toolkit.Mvvm.Input.IAsyncRelayCommand SaveFileAsyncCommand => saveFileAsyncCommand ??= new global::Microsoft.Toolkit.Mvvm.Input.AsyncRelayCommand(new global::System.Func<global::System.Threading.Tasks.Task>(SaveFileAsync));
    
    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    private global::Microsoft.Toolkit.Mvvm.Input.AsyncRelayCommand<global::MyApp.User>? logUserAsyncCommand;
    
    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator", "7.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public global::Microsoft.Toolkit.Mvvm.Input.IAsyncRelayCommand<global::MyApp.User> LogUserAsyncCommand => logUserAsyncCommand ??= new global::Microsoft.Toolkit.Mvvm.Input.AsyncRelayCommand<global::UnitTests.Mvvm.Test_ICommandAttribute.User>(new global::System.Func<global::UnitTests.Mvvm.Test_ICommandAttribute.User, global::System.Threading.Tasks.Task>(LogUserAsync));
}

Better performance

Another area that I want to investigate with source generators is possibly getting some performance improvemeents by removing reflection where possible. Now, the MVVM Toolkit is already quite light on reflection (as it was designed with that in mind, especially the messenger types), but I think there might be a few places where things could still be improved with source generators. For instance, this method uses quite a bit of reflection.

We could keep this for compatibility and also as a "fallback" implementation, but then we could have the source generator emit a type-specific version of this method with all the necessary handlers already specified, with no reflection. We'd just need to generate the appropriate method in the consuming assembly, and then the C# compiler would automatically pick that one up due to how overload resolution works (since the object recipient in the original method is less specific than a MyViewModel recipient parameter that the generated method would have). Still haven't done a working proof of concept for this point specifically, but it's next on my list and will update as soon as that's done too, just wanted to open this issue in the meantime to start gathering feedbacks and discuss ideas πŸ™‚

EDIT: I've now added a generator that will create a method for this for all types implementing IRecipient<T>:

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable
namespace Microsoft.Toolkit.Mvvm.Messaging.__Internals
{
    internal static partial class __IMessengerExtensions
    {
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("This method is not intended to be called directly by user code")]
        public static global::System.Action<IMessenger, object> CreateAllMessagesRegistrator(global::MyApp.MyViewModel _)
        {
            static void RegisterAll(IMessenger messenger, object obj)
            {
                var recipient = (global::MyApp.MyViewModel)obj;
                messenger.Register<global::MyApp.MessageA>(recipient);
                messenger.Register<global::MyApp.MessageB>(recipient);
            }

            return RegisterAll;
        }

        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("This method is not intended to be called directly by user code")]
        public static global::System.Action<IMessenger, object, TToken> CreateAllMessagesRegistratorWithToken<TToken>(global::MyApp.MyViewModel _)
            where TToken : global::System.IEquatable<TToken>
        {
            static void RegisterAll(IMessenger messenger, object obj, TToken token)
            {
                var recipient = (global::MyApp.MyViewModel)obj;
                messenger.Register<global::MyApp.MessageA, TToken>(recipient, token);
                messenger.Register<global::MyApp.MessageB, TToken>(recipient, token);
            }

            return RegisterAll;
        }
    }
}

This is then now picked up automatically when RegisterAll is called, so that the LINQ expression can be skipped entirely.
There are two generated methods so that the non-generic one can be used in the more common scenario where a registration token is not used, and that completely avoids runtime-code generation of all sorts and also more reflection (no more MakeDynamicMethod), making it particularly AOT-friendly πŸ˜„

EDIT 2: I've applied the same concept to the other place where I was using compiled LINQ expressions too, that is the ObservableValidator.ValidateAllProperties method. We now have a new generator that will process all types inheriting from ObservableValidator, and create helper methods like this that will then be loaded at runtime by the MVVM Toolkit as above:

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable
namespace Microsoft.Toolkit.Mvvm.ComponentModel.__Internals
{
    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableValidatorValidateAllPropertiesGenerator", "7.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
    [global::System.Obsolete("This type is not intended to be used directly by user code")]
    internal static partial class __ObservableValidatorExtensions
    {
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("This method is not intended to be called directly by user code")]
        public static global::System.Action<object> CreateAllPropertiesValidator(global::MyApp.PersonViewModel _)
        {
            static void ValidateAllProperties(object obj)
            {
                var instannce = (global::MyApp.PersonViewModel)obj;
                __ObservableValidatorHelper.ValidateProperty(instance, instance.Name, nameof(instance.Name));
                __ObservableValidatorHelper.ValidateProperty(instance, instance.Age, nameof(instance.Age));
            }

            return ValidateAllProperties;
        }
    }
}

When the source generators are used, the MVVM Toolkit is now 100% without dynamic code generation! πŸŽ‰

Tracking changes so far

  • [INotifyPropertyChangedAttribute]
  • [ObservableObjectAttribute]
  • [ObservableValidatorAttribute]
  • Additional support for generated RegisterAll method when possible
  • Additional support for generated ValidateAllProperties method when possible
  • [ObservableProperty]
  • [AlsoNotifyChangeFor]
  • [ICommand]
  • CanExecute property for [ICommand]
  • [AlsoNotifyCanExecuteFor]
  • Switch to incremental generators

Feedbacks and feature ideas are welcome! πŸ™Œ

Small Typo In ObservableProperty XML Docs

Describe the bug

I wasn't sure if this belonged in the other 'Docs' repo tracker, so I'll report it here since the code typo is within this repo.

There is a small typo in the XML doc for [ObservableProperty] found here:
https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/37caf88b68463ebb23b67c2e5d3f095485c9ecc5/Microsoft.Toolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs#L43

This should be isEnabled rather than name.

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:

Steps to Reproduce

  • Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...)

Steps to reproduce the behavior:

  1. No steps needed, just a typo in the doc.

Expected behavior

N/A

Screenshots

N/A

Environment

NuGet Package(s): CommunityToolkit.Mvvm

Package Version(s): 7.1.2

Windows 10 Build Number:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})
  • November 2021 Update (21H2)

App min and target version:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})
  • November 2021 Update (21H2)

Device form factor:

  • Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2017 (15.{minor_version})
  • 2019 (16.{minor_version})
  • 2022 (17.0.2)

Additional context

N/A

Microsoft.Toolkit.Mvvm v7.1.1 will freeze NUnit tests

Describe the bug

After upgrading Microsoft.Toolkit.Mvvm to v7.1.1 from v7.0.2 out unit tests are failing because their freeze after executing every test in the project. We use NUnit 4 to run tests. The problem is reproducible locally and on Azure Pipelines build machines.

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work: v7.0.2

Steps to Reproduce

We don't have a sample app as this is a proprietary software. We have application which uses Microsoft.Toolkit.Mvvm package and we have another unit tests project using NUnit.

Steps to reproduce the behavior:

  1. Upgrading Microsoft.Toolkit.Mvvm to v7.1.1
  2. Run unit tests using Visual Studio Test Explorer
  3. Wait for tests to finish.

Observe all tests were finished but Test Explorer reports tests are still running. This error is logged in the console:

Exception NUnit.Engine.NUnitEngineUnloadException,    Exception thrown unloading tests from C:\dev\ProjectA\tests\ProjectA.Tests\bin\Debug\ProjectA.Tests.dll
Unable to unload application domain: unload thread timed out after 30 seconds.
Application domain was unloaded before all details could be read.

   at NUnit.Engine.Services.DomainManager.DomainUnloader.Unload()
   at NUnit.Engine.Runners.TestDomainRunner.UnloadPackage()
   at NUnit.Engine.Runners.AbstractTestRunner.Unload()
   at NUnit.Engine.Runners.MasterTestRunner.UnloadPackage()
   at NUnit.Engine.Runners.MasterTestRunner.Unload()
   at NUnit.VisualStudio.TestAdapter.NUnitEngine.NUnitEngineAdapter.CloseRunner() in D:\repos\NUnit\nunit3-vs-adapter\src\NUnitTestAdapter\NUnitEngine\NUnitEngineAdapter.cs:line 128
   at NUnit.VisualStudio.TestAdapter.NUnit3TestExecutor.RunAssembly(String assemblyPath, IGrouping`2 testCases, TestFilter filter) in D:\repos\NUnit\nunit3-vs-adapter\src\NUnitTestAdapter\NUnit3TestExecutor.cs:line 305
NUnit Adapter 4.0.0.0: Test execution complete
An exception occurred while invoking executor 'executor://nunit3testexecutor/': Exception encountered unloading application domain
Stack trace:
   at NUnit.Engine.Services.DomainManager.DomainUnloader.Unload()
   at NUnit.Engine.Runners.TestDomainRunner.UnloadPackage()
   at NUnit.Engine.Runners.AbstractTestRunner.Unload()
   at NUnit.Engine.Runners.MasterTestRunner.UnloadPackage()
   at NUnit.Engine.Runners.MasterTestRunner.Unload()
   at NUnit.VisualStudio.TestAdapter.NUnitEngine.NUnitEngineAdapter.CloseRunner() in D:\repos\NUnit\nunit3-vs-adapter\src\NUnitTestAdapter\NUnitEngine\NUnitEngineAdapter.cs:line 128
   at NUnit.VisualStudio.TestAdapter.NUnitEngine.NUnitEngineAdapter.Dispose() in D:\repos\NUnit\nunit3-vs-adapter\src\NUnitTestAdapter\NUnitEngine\NUnitEngineAdapter.cs:line 132
   at NUnit.VisualStudio.TestAdapter.NUnitTestAdapter.Unload() in D:\repos\NUnit\nunit3-vs-adapter\src\NUnitTestAdapter\NUnitTestAdapter.cs:line 320
   at NUnit.VisualStudio.TestAdapter.NUnit3TestExecutor.RunTests(IEnumerable`1 tests, IRunContext runContext, IFrameworkHandle frameworkHandle) in D:\repos\NUnit\nunit3-vs-adapter\src\NUnitTestAdapter\NUnit3TestExecutor.cs:line 205
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution.RunTestsWithTests.InvokeExecutor(LazyExtension`2 executor, Tuple`2 executorUri, RunContext runContext, IFrameworkHandle frameworkHandle)
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution.BaseRunTests.<>c__DisplayClass48_0.<RunTestInternalWithExecutors>b__0()
   at Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformThread.<>c__DisplayClass0_0.<Run>b__0()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.PlatformThread.Run(Action action, PlatformApartmentState apartmentState, Boolean waitForCompletion)
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution.BaseRunTests.TryToRunInSTAThread(Action action, Boolean waitForCompletion)
   at Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution.BaseRunTests.RunTestInternalWithExecutors(IEnumerable`1 executorUriExtensionMap, Int64 totalTests)

Inner exception: Exception encountered unloading application domain: Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014)
Application domain was unloaded before all details could be read.

Expected behavior

There should be no errors when NUnit tries to unload the app domain.

Environment

NuGet Package(s): 7.1.1

Windows 10 Build Number:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

App min and target version:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • 21H1 Update (19043)
  • Insider Build ({build_number})

Device form factor:

  • Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2017 (15.{minor_version})
  • 2019 (16.11.5)
  • 2022 (17.{minor_version})

Switch all generated code to global::

Right now most generated types use global:: and the full type name, but there are still some cases where we're emitting either some using directives, or the non qualified type name relying on the fact it'd be in scope for the namespace the generated code will be located in. We should update all generators to always just emit fully qualified type names with the global:: prefix and no using directives. The only case where this can be avoided is when the type can be expressed with a built-in keyword (eg. object).

Usage of ObservableValidator in Windows Template Studio

Hi

We are working on a Mockup for WinUI3 Templates in Windows Template Studio that uses the MVVMToolkit libraries.
You can find the code at Mockup-WinUI3.

As part of this mockup we're looking at a FormPage that allows users to add a new order to an in-memory SampleOrder Collection.
We want to validate the user input and give visual feedback when the user edits any of the fields and when clicking submit.

We initially implemented a custom ValidationObservableRecipient and want to switch to the MVVMToolkit ObservableValidator now.
You can find a sample implementation of the old page in FormPage and the new implementation in the FormWCTPage.

We are having some issues in the new FormWCTPage:

  • The OrderId does not always remove the error message when a correct value is entered.
  • We did not find a way to validate and show visual feedback on the fields to the user when clicking submit button (for example: leave all fields empty and click submit)
  • We did not find a way to reset the fields to the default values after submitting

Are we missing something or are those scenarios not yet supported?

Screenshots of Form Page

image

Clicking directly on submit button

image

Thank you so much ;)

Code Generators degrade VS performance, add excessive load to Roslyn Analyzers and high CPU utilization

Describe the bug

Visual Studio C# Code Editors get very sluggish, Roslyn Analyzer with high CPU usage. No actual work can really get done in the editors.

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:
    Not observed on 7.0.2, after update to 7.1 this is showing. Reverting back to 7.0.2 is fixing this. Seems to be the Source Generators are loading up CPU to >50% up to 90% with the Roslyn Code Analyzers load.

Steps to Reproduce

Steps to reproduce the behavior:

  1. Update from 7.0.2 to 7.1 on a large project (unit is a 4 Core i7 with 4GB RAM on Windows 2012 Server development system).
  2. Start to code.
  3. Regular background utilization of >50% from Roslyn Code Analyzer, editing is sluggish, and not really possible.
  4. Revert back to 7.0.2
  5. Performance is back as used, VS is responsive again.

Expected behavior

Update to 7.1 should not degrade VS performance in editing, building.

NuGet Package(s): 7.1

Package Version(s):

Visual Studio version:

  • 2019 (16.11.3)

Add concurrency control to async relay commands

Describe the problem this feature would solve

Right now the two async command types in the MVVM Toolkit (AsyncRelayCommand and AsyncRelayCommand<T>) always allow asynchronous actions to be executed concurrently, if triggered more than once, with no easy way to just restrict only one to be executing at any given time. There should be built-in support for this that is easily configurable.

Describe the solution

This is the API breakdown for the proposed change:

  namespace CommunityToolkit.Mvvm.Input
 {
     public sealed class AsyncRelayCommand : IAsyncRelayCommand
     {
         public event EventHandler? CanExecuteChanged;
 
         public AsyncRelayCommand(Func<Task> execute);
+        public AsyncRelayCommand(Func<Task> execute, bool allowConcurrentExecutions);
         public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute);
+        public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, bool allowConcurrentExecutions);
         public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute);
+        public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute, bool allowConcurrentExecutions);
         public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, Func<bool> canExecute);
+        public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, Func<bool> canExecute, bool allowConcurrentExecutions);
     }
 
     public sealed class AsyncRelayCommand<T> : IAsyncRelayCommand<T>
     {
         public event EventHandler? CanExecuteChanged;

         public AsyncRelayCommand(Func<T?, Task> execute);
+        public AsyncRelayCommand(Func<T, Task> execute, bool allowConcurrentExecutions);
         public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute);
+        public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute, bool allowConcurrentExecutions);
         public AsyncRelayCommand(Func<T?, Task> execute, Predicate<T?> canExecute);
+        public AsyncRelayCommand(Func<T, Task> execute, Predicate<T> canExecute, bool allowConcurrentExecutions);
         public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute, Predicate<T?> canExecute);
+        public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute, Predicate<T> canExecute, bool allowConcurrentExecutions);
     }
 }

The default execution mode would be the same as today (when no allowConcurrentExecutions parameter is passed).
This ensures that existing consumers won't experience a difference in behavior when upgrading to a new version.

Describe alternatives you've considered

Just letting consumers handle this manually, which is extremely clunky and not really intuitive.

Strip source generator attributes using [Conditional]

Currently, all attributes used for the source generator feature are preserved in the compiled binaries, which just results in unnecessary binary size given these attributes are only ever used to guide the source generators at build time. We can strip them from the final assemblies by leveraging the [Conditional] attribute and using a dummy constant that final users will not have. Additionally, we might also set a custom one that we can then document, to still offer advanced users the ability to opt-in into preserving them, if needed.

Add trimming support for all packages

Planned for after #34 is merged. We should enable trimming support for all assemblies, and resolve any potential issues in case the trimming analyzers will bring anything up, or at least expose the right attributes ourselves to then inform our consumers.

[Proposal] LocalizationResourceManager

LocalizationResourceManager

  • Proposed
  • Prototype: Not Started
  • Implementation: Not Started
    • iOS Support
    • Android Support
    • macOS Support
    • Windows Support
  • Unit Tests: Not Started
  • Sample: Not Started
  • Documentation: Not Started

Summary

The LocalizationResourceManager class is a helper class that enables users to respond to culture changes at runtime.

Detailed Design

LocalizationResourceManager.shared.cs

public class LocalizationResourceManager : INotifyPropertyChanged
{
  public static LocalizationResourceManager Current;

  private LocalizationResourceManager();

  public void Init(ResourceManager resource);
  public void Init(ResourceManager resource, CultureInfo initialCulture);
  public object GetValue(string resourceKey);

  public object this[string resourceKey] => GetValue(resourceKey);

  public CultureInfo CurrentCulture { get; set; }
}

Usage Syntax

XAML Usage

N/A

C# Usage

LocalizationResourceManager.Current.PropertyChanged += (_, _) => AppResources.Culture = LocalizationResourceManager.Current.CurrentCulture;
LocalizationResourceManager.Current.Init(AppResources.ResourceManager);

LocalizationResourceManager.Current.CurrentCulture = newCulture;

Proposal: Allow Mvvm's ObservableObject's PropertyChanged event to fire on the thread that added the event handler

Summary

In some cases, mainly UI frameworks, it is required to fire events on the thread that added the event handler. For example, a multi window application may use and reuse the same view model on different User Interface Threads. Updating the UI would require the event to be fired on the correct thread in order to work.

A good example of an UI framework needing this, is Windows.UI.Xaml (details about a proposal to change its behavior here: microsoft/microsoft-ui-xaml#2795)

Details

This functionality cannot be added by an app developer without having to fork the library. Further more, forcing such functionality on every developer could break existing code behaviors, as well as add a performance impact on existing code which isn't needed.

A solution to this problem would be to make the behavior opt in, either by the use of a flag in the class, or by having another distinct class like SynchronizedObservableObject providing this functionality.

The former might require less code changes than the later, but would prevent an app developer from mixing both behaviors at will.

[Updated]

Aside from Windows.UI.Xaml the issue could be happening in other UI frameworks as well. Relying on the UI frameworks to change this behavior will always be the preferred option but unfortunately like in the case of Windows.UI.Xaml, changes may never come to previous versions of the framework that are supported on other platforms.

Further more

Some discussion has happened on this subject on the UWP Community Discord, about this problem and a potential way to solve it, more details here: https://canary.discord.com/channels/372137812037730304/669640275500466197/908427474747158588

Editor Issues While Using `[ObservableProperty]`

Describe the bug

While making use of the new [ObservableProperty] source generator, and potentially the other new generators, accessing the injected property names (and potentially other injected code) causes the editor to show errors.

As an example, here is a simple ViewModel that can reproduce the error:

namespace Launcher.ViewModel;

using CommunityToolkit.Mvvm.ComponentModel;

public partial class AboutViewModel : ObservableObject
{
    public AboutViewModel()
    {
        this.Test1 = "Hello world.";
    }

    [ObservableProperty]
    private string _test1;
}

Visual Studio marks a few things here that may need to be fixed:

  • The constructor is marked with a warning saying:
CS8618	Non-nullable field '_test1' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

This warning will appear regardless if you access the fields using the attribute or not. Just having the ctor is enough for them to cause this warning.

  • The set_ field access is marked as an error saying:
CS1061	'AboutViewModel' does not contain a definition for 'Test1' and no accessible extension method 'Test1' accepting a first argument of type 'AboutViewModel' could be found (are you missing a using directive or an assembly reference?)
  • The field itself is marked as:
IDE0044: Make field readonly.

This last one is kind of annoying since it puts the grey squares in the scrollbar of the editor for every single property that will make use of this source generator now.

To help visualize this, here is a screenshot of what my editor looks like using the above code:

devenv_aGxkpLhk0N

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:

Steps to Reproduce

  • Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...)

Steps to reproduce the behavior: (In a new project)

  1. Open VS2022, create a new project of your type choosing. (In my case, I am using WPF)
  2. Add a ViewModel folder and inside of it, add a new AboutViewModel class.
  3. Add a using to using CommunityToolkit.Mvvm.ComponentModel;.
  4. Change the class definition to partial.
  5. Add a private string property using the [ObservableProperty] attribute as shown above in the example code. (Name it _test1)
  6. Add a constructor. You should see the 2nd issue already now.
  7. Attempt to access the known injected property name in this new constructor. (this.Test1 = "Hello";) You should see the first issue now.
  8. You should also see the third issue already as well.

Steps to reproduce the behavior: (In the sample app)

  1. Clone the sample app repository.
  2. Open the Sample App in VS2022. (In my case, testing with the MvvmSampleUwp.sln)
  3. Update the project dependencies to latest Mvvm community toolkit version. (From 7.0.3 to 7.1.2 in Nuget manager.)
  4. Open the MvvmSample.Core > ViewModels > SamplePageViewModel.cs
  5. Change the class type to partial.
  6. Add a private string property using the [ObservableProperty] attribute as shown above in the example code. (Name it _test1)
  7. You should now see the second and third problem already. The constructor will warn about the field and the field will suggest being marked readonly.
  8. Attempt to access the known injected property name in this new constructor. (this.Test1 = "Hello";) You should see the first issue now.

Expected behavior

Based on the examples given and shown in the blog post about these new generators, there should be no errors, warnings, or suggestions while using the [ObservableProperty] attribute.

Screenshots

See above, the picture fits better with the explanation above than being separated and posted here.

Environment

NuGet Package(s):
Package Version(s):

  • CommunityToolkit.Mvvm (7.1.2)
  • Microsoft.Extensions.DependencyInjection (6.0.0)

Windows 10 Build Number:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})
  • November 2021 Update (21H2)

App min and target version:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})
  • November 2021 Update (21H2)

Device form factor:

  • Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2017 (15.{minor_version})
  • 2019 (16.{minor_version})
  • 2022 (17.0.2)

Additional context

Fix portable IsReferenceOrContainsReference<T> for pointer types

Overview

The IsReferenceOrContainsReferences(Type) method we use to polyfill on .NET Standard < 2.1 has a bug that was inherited from the portable implementation originally in the BCL, which causes pointer types or types containing pointer types to be reported as being reference types. This is caused by Type.IsValueType actually returning false in this case (for... Reasons? 🀷).

private static bool IsReferenceOrContainsReferences(Type type)

There should be an additional check for pointer types, and also a workaround for IsValueType.

[Feature] AsyncRelayCommand.Execute should catch and raise an Exception if possible

It easily happens that you use the AsyncRelayCommand somewhere as ICommand, if i call the ICommand.Execute i get no Exception, even if the async Method throws an Exception.

I know it's nearly impossible (only via AsyncEx.Run), to synchronous call an async method wait for it and raise the Exception in sync context.

Alternative:

AsyncRelayCommand should have a public static Event where i can register an EventHandler for these kind of Exceptions.

So Execute would look like this:

public async void Execute()
{
    try
    {
        await ExecuteAsync()
    }
    catch(Exception e)
    {
        ExceptionInSyncExecute?.Invoke(this, new UnhandledExceptionEventArgs(e));
    }
}

Make WeakReferenceMessenger broadcasting 0-alloc!

Needs #34 first, as we'll be using DependentHandle.
There are a number of performance/memory usage optimizations I have planned for WeakReferenceMessenger:

  • Leverage DependentHandle and a custom table to achieve 0-alloc enumeration
  • Investigate lock-free improvements as we know callers are synchronized
  • Investigate refactor detailed in dotnet/runtime#57495 (comment) to remove the Unsafe.As<T> usage to alias delegate types and also improve performance when invoking callbacks (by adding a fast path with explicit guarded devirtualization when invoking callbacks mapping to IRecipient<TMessage> directly, as opposed to having an additional delegate stub with closure).
  • Bring over new performance improvements to Dictionary<TKey, TValue> to the DictionarySlim<TKey, TValue> fork
  • Investigate reintroducing hashcode caching for better lookup performance when broadcasting

canExecute AsyncRelayCommand

Hello,

When using an AsyncRelayCommand, how can I get the canExecute function to be checked immediately after the command is fired so that the command cannot be fired again until the command has finished executing?

Thanks!

[Feature] ICommandGenerator should support canExecute relay command argument

Describe the problem this feature would solve

Currently there seems to be no way of using this source generator for relay commands that need to use the canExecute argument of RelayCommand/AsyncRelayCommand. This means that many commands will still have to be implemented the old way and now there are two ways of doing commands in a given project.

Describe the solution

I imagine a potential solution would be adding an optional parameter to ICommandAttribute, example:

private Task<bool> CanGreetUserAsync()
{
}

[ICommand(CanExecute = nameof(CanGreetUserAsync))]
private Task GreetUserAsync()
{
}

Describe alternatives you've considered

I guess it could also be inferred based on name alone, but that might not work well if code is written in other languages.

[Enhancement Request] Add auto-generated headers

Add auto-generated headers

Currently the source code generators, create the class with the following header:

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

This causes issues with code analyzers.

Add tags to the header

Adding auto-generated tags to the header will cause most code analyzers to automatically ignore the files.

// <auto-generated>
//   Licensed to the .NET Foundation under one or more agreements.
//   The .NET Foundation licenses this file to you under the MIT license.
//   See the LICENSE file in the project root for more information.
// </auto-generated>

[Feature] Introduce a source-only Microsoft.Toolkit.Diagnostics NuGet package

Describe the problem this feature would solve

The Microsoft.Toolkit.Diagnostics namespace contains the Guard and ThrowHelper APIs, which are very useful for developers on any platform and working on all kinds of applications, to do input validation and control flow management. Right now though they live in the base Microsoft.Toolkit package, which has a few downsides to it.

  • Contrary to other packages which have APIs that all belong to the same set of features or that pertain to a specific topic (eg. "Animations" or "Controls" or "Markdown"), the base Microsoft.Toolkit is a bit of a container for a number of different APIs that are unrelated from one another. Specifically in the context of developers purely interested in using the .Diagnostics APIs, having to also pull in unrelated code is not ideal. For example, the grouped collection types - which are more of a UI application type of APIs that might be completely out of place in other kinds of backend scenarios.
  • It includes an external dependency to the Microsoft.Toolkit package. I've spoken with a number of developers, and this is very often brought up as a main annoyance point, if not just a reason not to pick up the package at all. This is especially true for library authors, since those dependencies would transitively be inherited by consumers of those libraries as well, and be listed on NuGet, and just result in extra assemblies in the output folder of consumers. This can of course be absolutely fine in case of consumers being application developers, or just of consumers using bigger packages that are to be expected to involve a separate assembly (eg. the MVVM Toolkit, or any of the UWP packages), but it's often not received well or considered worth it in case of a very narrow scoped set of self-contained APIs such as the .Diagnostics ones.
  • Even if the package only contained the Guard and ThrowHelper APIs, those would still then be visible to consumers of those libraries, as indirect dependencies, even though the library authose only actually wanted to use them as internal helpers. Furtermore, because the Microsoft.Toolkit package contains some other unrelated APIs as well, consumers of those libraries would end up seeing those as well, even if they have nothing to do with the library they're installing.

Describe the solution

I feel that the Microsoft.Toolkit.Diagnostics namespace fits all the boxes to deserve to be moved to a standalone, self-contained, source-only NuGet package. This would greatly help with adoption especially for library consumers:

  • No additional project dependencies
  • No more transitive dependencies for consumers of those projects
  • No additional APIs exposed from consumers of those projects

For more info on source-only packages, here's a useful guide.

Further considerations

The Guard APIs shipped with the Microsoft.Toolkit 6.1 release, and we can't just remove them already. Plus I guess there could be many developers actually expecting of finding them there by default when referencing Microsoft.Toolkit or any of the other dependent packages. I'm thinking the Microsoft.Toolkit.Diagnostics package could just be an additional package that developers could choose to reference instead, without necessarily replacing the APIs in the base package. Again this would be very valuable especially for backend/library developers - they'd have the option to just use the source-only package purely as a development dependency (just like eg. StyleCop or Nerdbank.Versioning), without having their product actually depend on them on NuGet.

Case study: ImageSharp

ImageSharp is a very well known and widely used image processing library for .NET, arguably the best one there is. It's a .NET Foundation sponsored project, with 4.4K stars on GitHub and over 8 MILLION downloads on NuGet.

I proposed to use the Guard and ThrowHelper APIs to replace the internal helpers we're using there, but understandably Anton and James said it wasn't worth it for them to take an external dependency just for that. I spoke with @JimBobSquarePants yesterday about this new proposal for a source-only NuGet package, and he said he'd be on board to replace their helpers with our Guard and ThrowHelper APIs if we released them as a source-only package. I think having them adopted by such a big library would be a great way to showcase how useful these APIs are, and also to showcase how the Toolkit in general can be useful for library developers and cross platform developers that are just working on .NET Core/Standard, and not necessarily UWP. πŸš€

(ReadOnly)SpanView<T> and (ReadOnly)MemoryView<T>

Describe the problem this feature would solve

Currently you cannot conveniently manipulate strided memory via Memory<T> and Span<T>, as they have no support for stride. While it may soon be possible to manually create instances of (ReadOnly)RefEnumerable<T> (via CommunityToolkit/WindowsCommunityToolkit#3645), they cannot always be used for this purpose as they operate in terms of T and not byte.

Describe the solution

These four new types (SpanView<T>, ReadOnlySpanView<T>, MemoryView<T> and ReadOnlyMemoryView<T>) provide a solution to this problem by providing a strided "view" over an existing (ReadOnly)Span<T> or (ReadOnly)Memory<T>'s data. This allows safe and convenient access to strided data, such as interleaved mesh data buffers:

// ReadOnly variants omitted for brevity.
namespace Microsoft.Toolkit.HighPerformance.Memory
{
    public readonly ref struct SpanView<T>
        where T : unmanaged
    {
        public int Length { get; }
        public int Stride { get; }
        public bool IsEmpty { get; }
        public ref T this[int index] { get; }

        public static SpanView<T> Empty { get; }

        public static SpanView<T> DangerousCreate<TBuffer>(Span<TBuffer> buffer, ref T field) where TBuffer : unmanaged;
        public static SpanView<T> DangerousCreate<TBuffer>(Span<TBuffer> buffer, int offset) where TBuffer : unmanaged;

        public static implicit operator SpanView<T>(Span<T> span);
        public static bool operator ==(SpanView<T> left, SpanView<T> right);
        public static bool operator !=(SpanView<T> left, SpanView<T> right);

        public unsafe SpanView(void* pointer, int stride, int length);
        public SpanView(Span<byte> span, int offset, int stride);

        public Enumerator GetEnumerator();
        public SpanView<T> Slice(int start);
        public SpanView<T> Slice(int start, int length);
        public void Clear();
        public void Fill(T value);
        public void CopyFrom(ReadOnlySpan<T> source);
        public bool TryCopyFrom(ReadOnlySpan<T> source);
        public void CopyFrom(ReadOnlySpanView<T> source);
        public bool TryCopyFrom(ReadOnlySpanView<T> source);
        public void CopyTo(SpanView<T> destination);
        public bool TryCopyTo(SpanView<T> destination);
        public void CopyTo(Span<T> destination);
        public bool TryCopyTo(Span<T> destination);
        public ref T DangerousGetReference();
        public ref T DangerousGetReferenceAt(int index);
        public ref T GetPinnableReference();
        public bool Equals(SpanView<T> other);
        public override bool Equals(object obj); // NotSupportedException
        public override int GetHashCode(); // NotSupportedException
        public override string ToString();
        public T[] ToArray();

        public ref struct Enumerator
        {
            public ref T Current { get; }
            public bool MoveNext();
        }
    }

    public readonly struct MemoryView<T> : IEquatable<MemoryView<T>>
        where T : unmanaged
    {
        public int Length { get; }
        public int Stride { get; }
        public bool IsEmpty { get; }
        public SpanView<T> SpanView { get; }

        public static MemoryView<T> Empty { get; }

        public static MemoryView<T> DangerousCreate<TBuffer>(Memory<TBuffer> buffer, ref T field) where TBuffer : unmanaged;
        public static MemoryView<T> DangerousCreate<TBuffer>(Memory<TBuffer> buffer, int offset) where TBuffer : unmanaged;

        public static implicit operator MemoryView<T>(Memory<T> span);
        public static bool operator ==(MemoryView<T> left, MemoryView<T> right);
        public static bool operator !=(MemoryView<T> left, MemoryView<T> right);

        public MemoryView(Memory<byte> memory, int offset, int stride);

        public MemoryView<T> Slice(int start);
        public MemoryView<T> Slice(int start, int length);
        public void CopyTo(MemoryView<T> destination);
        public bool TryCopyTo(MemoryView<T> destination);
        public void CopyTo(Memory<T> destination);
        public bool TryCopyTo(Memory<T> destination);
        public bool Equals(MemoryView<T> other);
        public override bool Equals(object obj);
        public override int GetHashCode();
        public override string ToString();
        public T[] ToArray();
        public MemoryHandle Pin();
    }

    public static class MemoryViewMarshal
    {
        public static Memory<byte> GetMemory<T>(MemoryView<T> view) where T : unmanaged;
        public static ReadOnlyMemory<byte> GetMemory<T>(ReadOnlyMemoryView<T> view) where T : unmanaged;
        public static Span<byte> GetSpan<T>(SpanView<T> view) where T : unmanaged;
        public static ReadOnlySpan<byte> GetSpan<T>(ReadOnlySpanView<T> view) where T : unmanaged;
        
        public static MemoryView<TTo> Cast<TFrom, TTo>(MemoryView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;

        public static ReadOnlyMemoryView<TTo> Cast<TFrom, TTo>(ReadOnlyMemoryView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;

        public static SpanView<TTo> Cast<TFrom, TTo>(SpanView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;

        public static ReadOnlySpanView<TTo> Cast<TFrom, TTo>(ReadOnlySpanView<TFrom> view)
            where TFrom : unmanaged
            where TTo : unmanaged;
    }
}

Using the proposed API, the following code can be written:

// An interleaved vertex buffer for uploading mesh data to the GPU
struct Vertex
{
    public Vector3 Position;
    public Vector3 Normal;
    public Vector2 TexCoord;
}

// Allocate the vertex buffer
var vertices = new Span<Vertex>(new Vertex[100]);

// Create views over the fields
var positions = SpanView<Vector3>.DangerousCreate(vertices, ref vertices[0].Position);
var normals   = SpanView<Vector3>.DangerousCreate(vertices, ref vertices[0].Normal);
var coords    = SpanView<Vector2>.DangerousCreate(vertices, ref vertices[0].TexCoord);

// The interleaved data can now be iterated over field-wise
foreach (ref Vector3 position in positions)
{
    position += new Vector3(0, 1, 0);
}

A sample implementation of these types can be found here. Note that there are several optimizations and improvements that can be made (especially as this was written for .NET Standard 2.0). These are only intended to serve as a reference.

Unanswered questions

  • Can/should the unmanaged constraint be removed from the API? Currently it is necessitated by the use of (ReadOnly)Span<byte>.
  • How beneficial is the existence of MemoryViewMarshal?

Describe alternatives you've considered

Previously I proposed CommunityToolkit/WindowsCommunityToolkit#3641 to allow manual creation of (ReadOnly)RefEnumerable<T>, but it was found to be unsuitable for many of the scenarios these types are intended to solve.

MVVM Source Generator for app settings

Describe the problem this feature would solve

Today app developers have to write a lot of boilerplate code for app settings. Consider providing a source generator and an dd an AppSettingProperty attribute to simplify settings access as an observable property.

Describe the solution

Provide a new interface named ISettingsProvider which could be implemented by the class managing the settings. This way the app can use their own method to store and retrieve settings.

public interface ISettingsProvider
{
    T GetSetting<T>(string key, T fallback);
    void SetSetting<T>(string key, T value);
}

Then the developer could create an interface that inherits from ISettingsProvider and exposes the settings as properties with an attribute.

public interface IMyAppSettings : ISettingsProvider
{
    [AppSettingProperty(nameof(MyAppSetting, null))]
    object MyAppSetting { get; set; }
}

Implementation of the view model:

public sealed class SettingsViewModel : ObservableObject, IMyAppSettings
{
    T GetSetting<T>(string key, T fallback)
    {
        // TODO Implement settings read.
        return default(T);
    }

    void SetSetting<T>(string key, T value)
    {
        // TODO Implement settings write.
    }
}

The source generator would automatically generate the code for the MyAppSetting property with the help of the GetSetting and SetSetting methods. Generated code:

public object MyAppSetting
{
    get => GetSetting(nameof(MyAppSetting), null);
    set
    {
        if (MyAppSetting != value)
        {
            OnPropertyChanging(nameof(MyAppSetting));
            SetSetting(nameof(MyAppSetting), value);
            OnPropertyChanged(nameof(MyAppSetting));
        }
    }
}

Describe alternatives you've considered

None.

Investigate AppContext switches for opt-in/opt-out features

This is a tracking issue about experimenting with AppContext switches to customize the MVVM Toolkit.

Rationale

There are some proposed features (eg. #28) that cannot really be implemented in a transparent and pay-for-play with the current setup. They'd end up either requiring a separate type, or adding overhead for everyone. Similarly, some existing features, such as the INotifyPropertyChanging support in ObservableObject are not really needed by everyone, and it would be nice to have a way of disabling them. Obviously the interface cannot be removed at runtime, but it could just be not functional if desired.

The idea

We could experiment with adding a bunch of AppContext switches (see AppContext.SetSwitch and AppContext.TryGetSwitch) with names that we would then document, that would enable users to manually opt-in and out of specific features. The default values would remain consistent with the behavior today, but this would enable more experienced users to further customize the behavior of the MVVM Toolkit to suit their needs. Those switches would be cached into static readonly bool fields, so that checking them would be virtually free on modern runtimes (eg. when using tiered JIT, which is the default on CoreCLR, such fields would just become constants and be inlined when the method is recompiled at runtime, so the conditional branch would just vanish completely).

Feature items

  • Dispatcher-away invoke for property changed/changing events
  • Opt-out switch for INotifyPropertyChanging
  • ..?

Guard exception messages do not end in a period

Describe the bug

Exception messages produced by the Guard class from the Microsoft.Toolkit.Diagnostics package do not end in a period, which is inconsistent with the BCL, and doesn't follow Best practices for exceptions.

  • Is this bug a regression in the toolkit?

Steps to Reproduce

For instance, the following invocation:

Guard.IsNotNullOrEmpty(string.Empty, "parameter");

produces the following exception message:

Parameter "parameter" (string) must not be null or empty, was empty (Parameter 'parameter')

Expected behavior

There are a lot of inconsistencies in the exception messages produced by Guard in general, but in this particular case, it would be great to see something as follows:

Parameter 'parameter' (string) must not be null or empty, was empty.

Also notice the single quotes, because this is what Microsoft tends to use in the BCL, check Strings.resx for instance.

Environment

NuGet Package(s): Microsoft.Toolkit.Diagnostics
Package Version(s): 7.1.1

An option for disabling a button with the AsyncRelayCommand while the task is running.

Describe the problem this feature would solve

Users can push the command button many times while the asyn task is already running, causing the task to trriger many times.

Describe the solution

Adding a built in 'OnPropertyChanged()' for the 'IsRunning' property .
With a boolean in the constructor (somthing like 'bool disableWhileRunning = false) so the CanExecute property will bi allerted if the execution began or ended.

Describe alternatives you've considered

As of now, lots of self made solution for an AsyncRelayCommand can be found on the web.
It would be much easier the official Microsoft package had the opption in it so I won't have to find workarounds.

How add sorted item in ObservableGroupedCollection

Hello!
I have an example from the WindowsCommunityToolkit ObservableGroup (https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ObservableGroup).
Everything works. But all new groups and items are always added to the end. How can I add a new item (and a new group with a new item) in alphabetical order?
I tried: _contactsSource.Add(new ObservableGroup<string, Person>(groupName, new[] { newContact }));
And tried: ObservableGroupedCollectionExtensions.AddItem(_contactsSource, groupName, newContact);
But it did not help.

sort

Add [CallerArgumentExpression] support to Guard APIs

Describe the problem this feature would solve

C# 10 introduces support for the [CallerArgumentExpression] aqttribute, which lets the compiler access the argument of a given expression as input, without the user needing to type it out manually. This can be particularly beneficial in the Guard APIs, as it'd allow callers to avoid to specify the name of the parameter being checked in most cases. For instance:

// Before
Guard.IsNotNull(someVariable, nameof(someVariable));

// After
Guard.IsNotNull(someVariable);

And this would result in the same generated code. It would also still be possible to manually specify the inputs where needed.

Describe the solution

The complete diff is too big, but really it's just a matter of doing this same change for all APIs:

 namespace CommunityToolkit.Diagnostics
 {
     public static class Guard
     {
-        public static void IsNotNull<T>(T? value, string name);
+        public static void IsNotNull<T>(T? value, [CallerArgumentExpression("value")] string name = "");
     }
}

Proposal: UnsafeHelper class with IL-only As___() methods

See this issue I filed here: dotnet/runtime#62342

tl;dr this method would be highly useful, but it's unclear whether it will be approved and added to .NET itself. So, why not put an implementation here in the toolkit?

As for its implementation, it cannot be done in C#. It would have to be via InlineIL.Fody https://github.com/ltrzesniewski/InlineIL.Fody , or as a post-build event that runs a little program using Mono.Cecil to rewrite the IL (all 2 instructions of it!) of the relevant methods.

namespace System.Runtime.CompilerServices
{
    public static class Unsafe
    {
        // This name was chosen in part so it does not sort next to other methods like AsRef, 
        // therefore less probability it could be accidentally used (via auto-complete or etc.).
        // sizeof(U) must equal sizeof(T*)
        public static ref U AsPtrRef<T, U>(ref T* source)
            where T : unmanaged
            where U : unmanaged

        // The inverse operation is needed as well, to convert from ref U back to ref T*
        public static ref U* AsPtrRef<T, U>(ref T source)
            where T : unmanaged
            where U : unmanaged

        // Might want to have `ref T**` versions as well, up to a reasonable arity. `ref T***` perhaps, but `ref T*******` is a bit much. `T***` does _very occasionally_ pop up in native interop code (pointer to 2-dimensional array).
    }
}

@Sergio0694 will probably have a better name for this methods :)

Add .NET 6 target to all libraries

Now that .NET 6 is officially out, we should add support for it in all libraries.
This includes:

  • Common library
  • Diagnostics package
  • HighPerformance package
  • MVVM Toolkit
  • All unit test projects
  • Update CI script where needed

There are also a whole bunch of new improvements and optimizations that can be done once .NET 6 support is in, but we can leave them to separate, individual PRs. This one is just about setting up the necessary infrastructure to enable .NET 6 targets everywhere.

[Question] SetProperty not overloaded property in .NET 6 ?

Microsoft.Toolkit.Mvvm.ComponentModel.ObservableObject

I have a .NET 6 project with nullable enabled

I tried to apply this method in one of my property

SetProperty<T>(T oldValue, T newValue, Action<T> callback, [CallerMemberName] string? propertyName = null)

and I wrote something like below

SetProperty(ref selectedRole, value, t => { });

but VS seems to recognize my code into

SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, [CallerMemberName] string? propertyName = null)

this wrong method overload

Remove ObservableObject base from relay commands

Describe the problem this feature would solve

Right now both asynchronous command types inherit from ObservableObject.
This has a number of issues:

  • This also gives the type support for INotifyPropertyChanging, which is not applicable here.
  • This prevents us from being able to add a number of optimizations in the internal properties.

Describe the solution

The following changes need to be done to the two asynchronous command types:

 namespace CommunityToolkit.Mvvm.Input
 {
     public sealed class AsyncRelayCommand :
-        ObservableObject, 
         IAsyncRelayCommand
     {
+        public event PropertyChangedEventHandler? PropertyChanged;
     }
 
     public sealed class AsyncRelayCommand<T> :
-        ObservableObject,
         IAsyncRelayCommand<T>
     {
+        public event PropertyChangedEventHandler? PropertyChanged;
     }
 }

Along with this, some specific optimizations and refactorings can also be applied on their implementation.

Describe alternatives you've considered

We could leave them as they are, but that'd be suboptimal. This would likely not be source breaking for almost everyone.
Still leaving the breaking change label as this could technically be a binary breaking change.

Proposal: BitHelper.AndNot()

I have this in my Paint.NET codebase and it's pretty simple:

    /// <summary>
    /// Returns ~a &amp; b
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static uint AndNot(uint a, uint b)
    {
        if (Bmi1.IsSupported)
        {
            return Bmi1.AndNot(a, b);
        }
        else
        {
            return ~a & b;
        }
    }

    /// <summary>
    /// Returns ~a &amp; b
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ulong AndNot(ulong a, ulong b)
    {
        if (Bmi1.X64.IsSupported)
        {
            return Bmi1.X64.AndNot(a, b);
        }
        else
        {
            return ~a & b;
        }
    }

Add Config/Extension Suggestions for VS Code

Would be cool to ensure we have the vs config settings in the repo with recommended extensions so that we can build/run the project locally (or in CodeSpaces) in VS Code and run the project's Unit Tests.

ObservableValidator triggers warning SA1633 "The file header XML is invalid" when using StyleCop.Analyzers

Describe the bug

If you have a class inheriting from ObservableValidator from Microsoft.Toolkit.Mvvm.ComponentModel namespace, it would trigger a source code generator producing a file without a header. If code analysis is enabled in the project, such a file will be the subject of analysis.

Although the generated class is annotated with GeneratedCode attribute, it does not solve the issue because this attribute does not exclude the whole file, thus triggering warnings such as SA1633. This is critical for projects enforcing code style rules during build.

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work: 7.0.2

Steps to Reproduce

  1. Create a WPF project (either .NET 5/6 or .NET Framework).
  2. Add references to packages StyleCop.Analyzers and Microsoft.Toolkit.Mvvm (see exact versions below).
  3. Add stylecop.json configuration file to the project.
  4. Add a class that inherits from ObservableValidator, such as MainViewModel.
  5. Rebuild the project and observe the warning such as follows:

<...>\Microsoft.Toolkit.Mvvm.SourceGenerators\Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableValidatorValidateAllPropertiesGenerator\WpfAppCore.MainViewModel.cs(1,1,1,1): warning SA1633: The file header XML is invalid.

Expected behavior

The generated file should either contain // <auto-generated/> at the top of the file, or has an extension *.g.cs to tell Roslyn analyzers to exclude the file completely.

Environment

NuGet Package(s): Microsoft.Toolkit.Mvvm 7.1.1, StyleCop.Analyzers 1.1.118
Visual Studio version: 2019 (16.11.6)

Sample App->Helpers->MVVM Toolkit: Documentation shows "High Performance Package" doc, not MVVM doc

Describe the bug

In the Sample App ver 7.1.0, the MVVM Toolkit documentation contents is the "High Performance package" documentation. The MVVM Toolkit documentation is not displayed.

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:

Steps to Reproduce

In the Sample App, select Helpers and then MVVM Toolkit. Observe that the documentation displayed is not for the MVVM Toolkit, but is for the High Performance package.

  • Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...)

Yes.

Steps to reproduce the behavior:

  1. Given the following environment: The Sample App from the Windows Store on Windows 11
  2. Select the Helpers choice on the top toolbar.
  3. On the Developers row, click the MVVM Toolkit choice
  4. Observe that the documentation displayed is not for the MVVM Toolkit, but instead is for the High Performance Package

Expected behavior

I expected the MVVM Toolkit documentation.

Screenshots

Environment

NuGet Package(s):

Package Version(s):

Windows 10 Build Number:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})
    -[X] WINDOWS 11

App min and target version:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

Device form factor:

  • [X ] Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2017 (15.{minor_version})
  • 2019 (16.{minor_version})
  • 2022 (17.{minor_version})

Additional context

Adding ObservableProperty to a ViewModel cause class definitions error

Description

I created a simple WinUI 3.0 app using Blank App, Packaged (WinUI 3 in Desktop) template.
I added Microsoft.Toolkit.Mvvm and tried to use ObservableProperty source generators.

With these lines in my MainViewModel.cs file:

[ObservableProperty]
private string title = "My title";

I get this error:

1>C:\Users\lucaspol\source\MyAppsMezzo\Microsoft.Toolkit.Mvvm.SourceGenerators\Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator\MyAppsMezzo.ViewModels.MainViewModel.cs(7,18,7,31): error CS0101: The namespace 'MyAppsMezzo.ViewModels' already contains a definition for 'MainViewModel'

If I remove the previous lines, I don't get any error.

@Sergio0694 please take a look.

Steps to Reproduce

attaching sample project: MyAppsMezzo.zip

Environment

NuGet Package(s):
[see attached sample project]

Windows 11 Build Number: 22489.1000

App min and target version:
Min: May 2020 Update (19041)
Target: 10.0.22000.0

Device form factor:

  • Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2022 (17.1.0)

MVVM Source Generators: Renaming certain names caught by generators fails.

Describe the bug

This might belong somewhere else but somewhere here might be able to help me find out the source of the issue.

Trying to rename a member var or its type that has the ObservablePropertyAttribute on it or rename a method name or the type of its parameter that has the ICommandAttribute on it the rename operation fails with an error.

A clear and concise description of what the bug is.

  • Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:

Steps to Reproduce

  • Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...)

Steps to reproduce the behavior:

  1. Given this code
    public struct Foo { int a; }
    public struct Faa { int a; }

    [ObservableObject]
    public class TestViewModel
    {
        [ObservableProperty]
        private Foo bar;

        [ICommand]
        private void DoBiz(Faa a) { }
    }
  1. Try to rename Foo, Faa, bar or DoBiz using visual studio's rename functionality (Commonly bound to Ctrl+R, Ctrl+R)
  2. See the rename dialog stuck with the "Searching..." text and no count of the amount of occurrences of the rename target.
  3. Change the rename target's name.
  4. Hit "Apply" on the rename dialog.
  5. See visual studio error message.

Expected behavior

Able to rename regardless if the name is captured in a source generator.

Screenshots

Environment

NuGet Package(s):

Package Version(s):

Windows 10 Build Number:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

App min and target version:

  • Fall Creators Update (16299)
  • April 2018 Update (17134)
  • October 2018 Update (17763)
  • May 2019 Update (18362)
  • May 2020 Update (19041)
  • Insider Build ({build_number})

Device form factor:

  • Desktop
  • Xbox
  • Surface Hub
  • IoT

Visual Studio version:

  • 2017 (15.{minor_version})
  • 2019 (16.11.2)
  • 2022 (17.{minor_version})

Additional context

The stack trace from the error message

System.InvalidOperationException : The solution does not contain the specified document.
   at Microsoft.CodeAnalysis.Shared.Extensions.ISolutionExtensions.GetRequiredDocument(Solution solution,DocumentId documentId)
   at async Microsoft.CodeAnalysis.Rename.SerializableRenameLocation.RehydrateAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.Rename.SerializableSearchResult.RehydrateAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.Rename.RenameLocations.TryRehydrateAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.Rename.RenameLocations.FindLocationsAsync(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.AbstractEditorInlineRenameService.SymbolInlineRenameInfo.FindRenameLocationsAsync(<Unknown Parameters>)
   at async Microsoft.VisualStudio.Threading.JoinableTask.JoinAsync(<Unknown Parameters>)
   at async Microsoft.VisualStudio.Threading.JoinableTask`1.<JoinAsync>g__JoinSlowAsync|3_0[T](<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.InlineRenameSession.<>c__DisplayClass39_0.<UpdateReferenceLocationsTask>b__0(<Unknown Parameters>)
   at async Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.InlineRenameSession.<>c__DisplayClass69_0.<UpdateConflictResolutionTask>b__0(<Unknown Parameters>)
   at Microsoft.VisualStudio.Threading.JoinableTask.Join(CancellationToken cancellationToken)
   at Microsoft.VisualStudio.Threading.JoinableTask`1.Join(CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.InlineRenameSession.CommitCore(IWaitContext waitContext,Boolean previewChanges)
   at Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.InlineRenameSession.<>c__DisplayClass78_0.<CommitWorker>b__0(IWaitContext waitContext)
   at Microsoft.VisualStudio.LanguageServices.Implementation.Utilities.VisualStudioWaitIndicator.Wait(String title,String message,Boolean allowCancel,Boolean showProgress,Action`1 action)
   at Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.InlineRenameSession.CommitWorker(Boolean previewChanges)
   at Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.Dashboard.Commit()
   at Microsoft.VisualStudio.Telemetry.WindowsErrorReporting.WatsonReport.GetClrWatsonExceptionInfo(Exception exceptionObject)

[Question] Red border always visible

Hello, I have a problem with validations. The validation border is still present after performing the validation of the property. I work with WPF

I create a course list (with 2 properties: start and end). For each race I pass the course list as a parameter.

Course class :

    public class Course : ObservableValidator
    {
        #region Fields

        private IEnumerable<Course> _courses;

        private int _start;

        private int _end;

        #endregion

        #region Properties

        [CustomValidation(typeof(Course), nameof(ValidateStart))]
        public int Start
        {
            get { return _start; }
            set
            {
                SetProperty(ref this._start, value, true);
                // ValidateProperty(this.End, nameof(End));
                ValidateCourses();
            }
        }

        [CustomValidation(typeof(Course), nameof(ValidateEnd))]
        public int End
        {
            get { return _end; }
            set
            {
                SetProperty(ref this._end, value, true);
                // ValidateProperty(this.Start, nameof(Start));
                ValidateCourses();
            }
        }

        #endregion

        #region Constructors

        public Course(IEnumerable<Course> courses)
        {
            _courses = courses;
                ValidateCourses();
        }

        #endregion

        #region Methods

        private void ValidateCourses()
        {
            foreach (var course in _courses)
                course.ValidateAllProperties();
        }

        #endregion

        #region Validations

        public static ValidationResult ValidateStart(int start, ValidationContext context)
        {
            Course course = (Course)context.ObjectInstance;

            List<Course> courses = course._courses.ToList();

            List<string> results = new List<string>();

            int index = courses.IndexOf(course);

            if (index == 0 && start != 0)
                results.Add("Start must be equal to zero");

            if (start >= course.End)
                results.Add("Start must be less end");

            if (index != -1 && index - 1 != -1)
                if (start != courses[index - 1].End)
                    results.Add("Start must be equal previous end");

            if (results.Count == 0)
            {
                return ValidationResult.Success;
            }

            return new ValidationResult(string.Join("\n", results), new List<string>() { nameof(Course.Start) });
        }

        public static ValidationResult ValidateEnd(int end, ValidationContext context)
        {
            Course course = (Course)context.ObjectInstance;

            List<Course> courses = course._courses.ToList();

            List<string> results = new List<string>();

            int index = courses.IndexOf(course);

            if (course.Start >= end)
                results.Add("End must be greater Start");

            if (index + 1 <= courses.Count - 1)
                if (end != courses[index + 1].Start)
                    results.Add("End must be equal next Start");

            if (results.Count == 0)
            {
                return ValidationResult.Success;
            }

            return new ValidationResult(string.Join("\n", results), new List<string>() { nameof(Course.End) });
        }

        #endregion
    }

MainViewModel class :

 public class MainViewModel : ObservableObject
    {
        #region Fields

        private ObservableCollection<Course> _courses = new ObservableCollection<Course>();

        #endregion

        #region Properties

        public ObservableCollection<Course> Courses
        {
            get { return _courses; }
            set { _courses = value; }
        }

        #endregion

        #region Constructors

        public MainViewModel()
        {
            CreateCourses();
        }

        #endregion

        #region Methods

        private void CreateCourses()
        {
            Courses.Add(new Course(Courses)
            {
                Start = 0,
                End = 10
            });
            Courses.Add(new Course(Courses)
            {
                Start = 11,
                End = 20
            });
            Courses.Add(new Course(Courses)
            {
                Start = 21,
                End = 30
            });
            Courses.Add(new Course(Courses)
            {
                Start = 31,
                End = 30
            });
        }

        #endregion
    }

MainWindow class

<DataGrid ItemsSource="{Binding Courses, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
                  AutoGenerateColumns="False">
            <DataGrid.Resources>
                <Style x:Key="errorStyle" TargetType="{x:Type TextBlock}">
                    <Style.Triggers>
                        <Trigger Property="Validation.HasError" Value="True">
                            <Setter Property="ToolTip" 
                                    Value="{Binding RelativeSource={RelativeSource Self},
                                Path=(Validation.Errors)[0].ErrorContent}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Start TextColumn"
                                    ElementStyle="{StaticResource errorStyle}"
                                    Binding="{Binding Start, ValidatesOnExceptions=True}"/>
                <DataGridTextColumn Header="End TextColumn"
                                    ElementStyle="{StaticResource errorStyle}"
                                    Binding="{Binding End, ValidatesOnExceptions=True}"/>

                <DataGridTemplateColumn Header="Start TemplateColumn (TextBox)">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Start, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}">
                                <TextBox.Style>
                                    <Style TargetType="TextBox">
                                        <Style.Triggers>
                                            <Trigger Property="Validation.HasError" Value="true">
                                                <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}"/>
                                            </Trigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBox.Style>
                            </TextBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="End TemplateColumn (TextBox)">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding End, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}">
                                <TextBox.Style>
                                    <Style TargetType="TextBox">
                                        <Style.Triggers>
                                            <Trigger Property="Validation.HasError" Value="true">
                                                <Setter Property="Validation.ErrorTemplate">
                                                    <Setter.Value>
                                                        <ControlTemplate>
                                                            <AdornedElementPlaceholder/>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}"/>
                                            </Trigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBox.Style>
                            </TextBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

When I edit a property, I check all the properties in the course list.

My problem is that the border still appears even though the property returned ValidationResult.Success

we can see that in the image below

image

I attach the test project.
ProjetTest.zip

Please can you help me

MVVM Toolkit not correctly unloading using Appdomain.Unload() with Version 7.1

Hello!

After updating the version of the Toolkit from 7.0 to 7.1 we cannot unload the Appdomain correctly anymore.
Some background: in our Application we start another Appdomain containing the View that uses the MVVM Toolkit. After closing the View we also unload the Appdomain (via AppDomain.Unload(domain)) to properly unload everything. After updating the Toolkit from 7.0 to 7.1 this unload process runs forever (>10 minutes) and slows/blocks any further code being run in our Application. Going back to Version 7.0 fixes the issue. If you need furthe rInformation regarding this issue please reach out to me, I will help in any way I can.

Best Regards
Chris

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.