diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs
index 8b36b73af..9c19e073e 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs
@@ -115,11 +115,13 @@ public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersA
AttributeArgument(ParseExpression("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods"))))));
}
+ // Create it in the standard Messaging namespace
+ // so that it resolves naturally from the user's code.
// This code produces a compilation unit as follows:
//
// //
// #pragma warning disable
- // namespace CommunityToolkit.Mvvm.Messaging.__Internals
+ // namespace CommunityToolkit.Mvvm.Messaging
// {
//
// internal static partial class __IMessengerExtensions
@@ -128,7 +130,7 @@ public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersA
// }
return
CompilationUnit().AddMembers(
- NamespaceDeclaration(IdentifierName("CommunityToolkit.Mvvm.Messaging.__Internals")).WithLeadingTrivia(TriviaList(
+ NamespaceDeclaration(IdentifierName("CommunityToolkit.Mvvm.Messaging")).WithLeadingTrivia(TriviaList(
Comment("// "),
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)))).AddMembers(
ClassDeclaration("__IMessengerExtensions").AddModifiers(
@@ -146,151 +148,86 @@ public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersA
/// The generated instance for .
public static CompilationUnitSyntax GetSyntax(RecipientInfo recipientInfo)
{
- // Create a static factory method to register all messages for a given recipient type.
- // This follows the same pattern used in ObservableValidatorValidateAllPropertiesGenerator,
- // with the same advantages mentioned there (type safety, more AOT-friendly, etc.).
+ // Create a static method to register all messages for a given recipient type.
+ // This pattern is used so that the library doesn't have to
+ // use GetType(...) and GetMethod(...) at runtime.
+ // This pattern eliminates the need for reflection entirely
+ // because type resolution occurs at "compile time" rather than at runtime.
// This is the first overload being generated: a non-generic method doing the registration
// with no tokens, which is the most common scenario and will help particularly with AOT.
// This code will produce a syntax tree as follows:
//
// ///
- // /// Creates a message registration stub for objects.
+ // /// Registers all declared message handlers for a given recipient, using the default channel.
// ///
- // /// Dummy parameter, only used to disambiguate the method signature.
- // /// A message registration stub for objects.
- // [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 CreateAllMessagesRegistrator( _)
+ // ///
+ // public static void RegisterAll(this global::CommunityToolkit.Mvvm.Messaging.IMessenger messenger, recipient)
// {
- // static void RegisterAll(global::CommunityToolkit.Mvvm.Messaging.IMessenger messenger, object obj)
- // {
- // var recipient = ()obj;
- //
- // }
- //
- // return RegisterAll;
+ //
// }
MethodDeclarationSyntax defaultChannelMethodDeclaration =
MethodDeclaration(
- GenericName("global::System.Action").AddTypeArgumentListArguments(
- IdentifierName("global::CommunityToolkit.Mvvm.Messaging.IMessenger"),
- PredefinedType(Token(SyntaxKind.ObjectKeyword))),
- Identifier("CreateAllMessagesRegistrator")).AddAttributeLists(
- AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
- AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never")))))
- .WithOpenBracketToken(Token(TriviaList(
- Comment("/// "),
- Comment($"/// Creates a message registration stub for objects."),
- Comment("/// "),
- Comment("/// Dummy parameter, only used to disambiguate the method signature."),
- Comment($"/// A message registration stub for objects.")), SyntaxKind.OpenBracketToken, TriviaList())),
- AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
- AttributeArgument(LiteralExpression(
- SyntaxKind.StringLiteralExpression,
- Literal("This method is not intended to be called directly by user code"))))))).AddModifiers(
+ PredefinedType(Token(SyntaxKind.VoidKeyword)),
+ Identifier("RegisterAll"))
+ .AddModifiers(
Token(SyntaxKind.PublicKeyword),
- Token(SyntaxKind.StaticKeyword)).AddParameterListParameters(
- Parameter(Identifier("_")).WithType(IdentifierName(recipientInfo.TypeName)))
- .WithBody(Block(
- LocalFunctionStatement(
- PredefinedType(Token(SyntaxKind.VoidKeyword)),
- Identifier("RegisterAll"))
- .AddModifiers(Token(SyntaxKind.StaticKeyword))
- .AddParameterListParameters(
- Parameter(Identifier("messenger")).WithType(IdentifierName("global::CommunityToolkit.Mvvm.Messaging.IMessenger")),
- Parameter(Identifier("obj")).WithType(PredefinedType(Token(SyntaxKind.ObjectKeyword))))
- .WithBody(Block(
- LocalDeclarationStatement(
- VariableDeclaration(IdentifierName("var"))
- .AddVariables(
- VariableDeclarator(Identifier("recipient"))
- .WithInitializer(EqualsValueClause(
- CastExpression(
- IdentifierName(recipientInfo.TypeName),
- IdentifierName("obj")))))))
- .AddStatements(EnumerateRegistrationStatements(recipientInfo).ToArray())),
- ReturnStatement(IdentifierName("RegisterAll"))));
+ Token(SyntaxKind.StaticKeyword))
+ .WithLeadingTrivia(TriviaList(
+ Comment("/// "),
+ Comment($"/// Registers all declared message handlers for a given recipient, using the default channel."),
+ Comment("/// "),
+ Comment("/// ")))
+ .AddParameterListParameters(
+ Parameter(Identifier("messenger"))
+ .AddModifiers(Token(SyntaxKind.ThisKeyword))
+ .WithType(IdentifierName("global::CommunityToolkit.Mvvm.Messaging.IMessenger")),
+ Parameter(Identifier("recipient")).WithType(IdentifierName(recipientInfo.TypeName)))
+ .WithBody(Block(EnumerateRegistrationStatements(recipientInfo).ToArray()));
// Create a generic version that will support all other cases with custom tokens.
- // Note: the generic overload has a different name to simplify the lookup with reflection.
// This code will produce a syntax tree as follows:
//
// ///
- // /// Creates a message registration stub for objects, with an input token.
+ // /// Registers all declared message handlers for a given recipient.
// ///
- // /// The type of tokens that will be used to register messages.
- // /// Dummy parameter, only used to disambiguate the method signature.
- // /// A message registration stub for objects.
- // [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 CreateAllMessagesRegistratorWithToken( _)
- // where TToken : global::System.IEquatable
+ // ///
+ // public static void RegisterAll(global::CommunityToolkit.Mvvm.Messaging.IMessenger messenger, recipient, TToken token)
+ // where TToken : notnull, global::System.IEquatable
// {
- // static void RegisterAll(global::CommunityToolkit.Mvvm.Messaging.IMessenger messenger, object obj, TToken token)
- // {
- // var recipient = ()obj;
- //
- // }
- //
- // return RegisterAll;
+ //
// }
MethodDeclarationSyntax customChannelMethodDeclaration =
MethodDeclaration(
- GenericName("global::System.Action").AddTypeArgumentListArguments(
- IdentifierName("global::CommunityToolkit.Mvvm.Messaging.IMessenger"),
- PredefinedType(Token(SyntaxKind.ObjectKeyword)),
- IdentifierName("TToken")),
- Identifier("CreateAllMessagesRegistratorWithToken")).AddAttributeLists(
- AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
- AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never")))))
- .WithOpenBracketToken(Token(TriviaList(
- Comment("/// "),
- Comment($"/// Creates a message registration stub for objects."),
- Comment("/// "),
- Comment("/// The type of tokens that will be used to register messages."),
- Comment("/// Dummy parameter, only used to disambiguate the method signature."),
- Comment($"/// A message registration stub for objects.")), SyntaxKind.OpenBracketToken, TriviaList())),
- AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
- AttributeArgument(LiteralExpression(
- SyntaxKind.StringLiteralExpression,
- Literal("This method is not intended to be called directly by user code"))))))).AddModifiers(
+ PredefinedType(Token(SyntaxKind.VoidKeyword)),
+ Identifier("RegisterAll"))
+ .AddModifiers(
Token(SyntaxKind.PublicKeyword),
- Token(SyntaxKind.StaticKeyword)).AddParameterListParameters(
- Parameter(Identifier("_")).WithType(IdentifierName(recipientInfo.TypeName)))
- .AddTypeParameterListParameters(TypeParameter("TToken"))
- .AddConstraintClauses(
- TypeParameterConstraintClause("TToken")
- .AddConstraints(TypeConstraint(GenericName("global::System.IEquatable").AddTypeArgumentListArguments(IdentifierName("TToken")))))
- .WithBody(Block(
- LocalFunctionStatement(
- PredefinedType(Token(SyntaxKind.VoidKeyword)),
- Identifier("RegisterAll"))
- .AddModifiers(Token(SyntaxKind.StaticKeyword))
- .AddParameterListParameters(
- Parameter(Identifier("messenger")).WithType(IdentifierName("global::CommunityToolkit.Mvvm.Messaging.IMessenger")),
- Parameter(Identifier("obj")).WithType(PredefinedType(Token(SyntaxKind.ObjectKeyword))),
- Parameter(Identifier("token")).WithType(IdentifierName("TToken")))
- .WithBody(Block(
- LocalDeclarationStatement(
- VariableDeclaration(IdentifierName("var"))
- .AddVariables(
- VariableDeclarator(Identifier("recipient"))
- .WithInitializer(EqualsValueClause(
- CastExpression(
- IdentifierName(recipientInfo.TypeName),
- IdentifierName("obj")))))))
- .AddStatements(EnumerateRegistrationStatementsWithTokens(recipientInfo).ToArray())),
- ReturnStatement(IdentifierName("RegisterAll"))));
+ Token(SyntaxKind.StaticKeyword))
+ .WithLeadingTrivia(TriviaList(
+ Comment("/// "),
+ Comment($"/// Registers all declared message handlers for a given recipient."),
+ Comment("/// "),
+ Comment("/// ")))
+ .AddTypeParameterListParameters(TypeParameter("TToken"))
+ .AddConstraintClauses(
+ TypeParameterConstraintClause("TToken")
+ .AddConstraints(
+ TypeConstraint(IdentifierName("notnull")),
+ TypeConstraint(GenericName("global::System.IEquatable").AddTypeArgumentListArguments(IdentifierName("TToken")))))
+ .AddParameterListParameters(
+ Parameter(Identifier("messenger"))
+ .AddModifiers(Token(SyntaxKind.ThisKeyword))
+ .WithType(IdentifierName("global::CommunityToolkit.Mvvm.Messaging.IMessenger")),
+ Parameter(Identifier("recipient")).WithType(IdentifierName(recipientInfo.TypeName)),
+ Parameter(Identifier("token")).WithType(IdentifierName("TToken")))
+ .WithBody(Block(EnumerateRegistrationStatementsWithTokens(recipientInfo).ToArray()));
// This code produces a compilation unit as follows:
//
// //
// #pragma warning disable
- // namespace CommunityToolkit.Mvvm.Messaging.__Internals
+ // #nullable enable
+ // namespace CommunityToolkit.Mvvm.Messaging
// {
// ///
// partial class __IMessengerExtensions
@@ -300,9 +237,10 @@ public static CompilationUnitSyntax GetSyntax(RecipientInfo recipientInfo)
// }
return
CompilationUnit().AddMembers(
- NamespaceDeclaration(IdentifierName("CommunityToolkit.Mvvm.Messaging.__Internals")).WithLeadingTrivia(TriviaList(
+ NamespaceDeclaration(IdentifierName("CommunityToolkit.Mvvm.Messaging")).WithLeadingTrivia(TriviaList(
Comment("// "),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)))).AddMembers(
+ Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
+ Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)))).AddMembers(
ClassDeclaration("__IMessengerExtensions").AddModifiers(
Token(TriviaList(Comment("/// ")), SyntaxKind.PartialKeyword, TriviaList()))
.AddMembers(defaultChannelMethodDeclaration, customChannelMethodDeclaration)))
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs
index 5cd9433e6..fd58c22f6 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableRecipient.cs
@@ -54,22 +54,18 @@ protected ObservableRecipient(IMessenger messenger)
///
/// Gets or sets a value indicating whether the current view model is currently active.
///
+ ///
+ /// When this property is set to , the OnActivated() method will be invoked,
+ /// which will register all necessary message handlers for this recipient.
+ ///
public bool IsActive
{
get => this.isActive;
- [RequiresUnreferencedCode(
- "When this property is set to true, the OnActivated() method will be invoked, which will register all necessary message handlers for this recipient. " +
- "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
- "If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
- "path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
- "Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
[RequiresDynamicCode(
- "When this property is set to true, the OnActivated() method will be invoked, which will register all necessary message handlers for this recipient. " +
- "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
- "If that is present, the method is AOT safe, as the only methods being invoked to register the messages will be the ones produced by the source generator. " +
- "If it isn't, this method will need to dynamically create the generic methods to register messages, which might not be available at runtime. " +
- "Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
+ "\nEnsure that the calling view model implements the `IRecipient` interface(s), " +
+ "or that `OnActivated()` has been manually overridden to register each required message for this recipient individually. " +
+ "If either of these conditions is met, you can suppress this warning by using the `UnconditionalSuppressMessage` attribute.")]
set
{
if (SetProperty(ref this.isActive, value, true))
@@ -91,22 +87,21 @@ public bool IsActive
/// Use this method to register to messages and do other initialization for this instance.
///
///
+ ///
/// The base implementation registers all messages for this recipients that have been declared
/// explicitly through the interface, using the default channel.
/// For more details on how this works, see the method.
+ ///
+ ///
/// If you need more fine tuned control, want to register messages individually or just prefer
/// the lambda-style syntax for message registration, override this method and register manually.
+ ///
///
- [RequiresUnreferencedCode(
- "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
- "If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
- "path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type. " +
- "Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
[RequiresDynamicCode(
- "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
- "If that is present, the method is AOT safe, as the only methods being invoked to register the messages will be the ones produced by the source generator. " +
- "If it isn't, this method will need to dynamically create the generic methods to register messages, which might not be available at runtime. " +
- "Alternatively, OnActivated() can be manually overwritten, and registration can be done individually for each required message for this recipient.")]
+ "Ensure that the view model implements the `IRecipient` interface(s), " +
+ "Alternatively, `OnActivated()` can be manually overwritten, and registration can be done individually for each required message for this recipient. " +
+ "If either of these conditions is met, you can suppress this warning by using the `UnconditionalSuppressMessage` attribute.",
+ Url = "https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/observablerecipient#how-it-works")]
protected virtual void OnActivated()
{
Messenger.RegisterAll(this);
diff --git a/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs
index 532639c8f..83a611311 100644
--- a/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs
+++ b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs
@@ -86,47 +86,18 @@ public static bool IsRegistered(this IMessenger messenger, object reci
/// The recipient that will receive the messages.
/// See notes for for more info.
/// Thrown if or are .
- [RequiresUnreferencedCode(
- "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
- "If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
- "path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type.")]
[RequiresDynamicCode(
- "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
- "If that is present, the method is AOT safe, as the only methods being invoked to register the messages will be the ones produced by the source generator. " +
- "If it isn't, this method will need to dynamically create the generic methods to register messages, which might not be available at runtime.")]
+ "\nThis fallback method uses `MethodInfo.MakeGenericMethod(Type[])` and " +
+ "to dynamically construct delegates. " +
+ "This will fail in Native AOT if the specific generic instantiations were not generated. " +
+ "Ensure the exact recipient type implements the `CommunityToolkit.Mvvm.Messaging.IRecipient` interface(s) and " +
+ "is statically known at compile time to use the source-generated, AOT-safe fast path.")]
public static void RegisterAll(this IMessenger messenger, object recipient)
{
ArgumentNullException.ThrowIfNull(messenger);
ArgumentNullException.ThrowIfNull(recipient);
- // We use this method as a callback for the conditional weak table, which will handle
- // thread-safety for us. This first callback will try to find a generated method for the
- // target recipient type, and just invoke it to get the delegate to cache and use later.
- [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
- static Action? LoadRegistrationMethodsForType(Type recipientType)
- {
- if (recipientType.Assembly.GetType("CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions") is Type extensionsType &&
- extensionsType.GetMethod("CreateAllMessagesRegistrator", new[] { recipientType }) is MethodInfo methodInfo)
- {
- return (Action)methodInfo.Invoke(null, new object?[] { null })!;
- }
-
- return null;
- }
-
- // Try to get the cached delegate, if the generator has run correctly
- Action? registrationAction = DiscoveredRecipients.RegistrationMethods.GetValue(
- recipient.GetType(),
- LoadRegistrationMethodsForType);
-
- if (registrationAction is not null)
- {
- registrationAction(messenger, recipient);
- }
- else
- {
- messenger.RegisterAll(recipient, default(Unit));
- }
+ messenger.RegisterAll(recipient, default(Unit));
}
///
@@ -137,18 +108,35 @@ public static void RegisterAll(this IMessenger messenger, object recipient)
/// The recipient that will receive the messages.
/// The token indicating what channel to use.
///
- /// This method will register all messages corresponding to the interfaces
- /// being implemented by . If none are present, this method will do nothing.
- /// Note that unlike all other extensions, this method will use reflection to find the handlers to register.
- /// Once the registration is complete though, the performance will be exactly the same as with handlers
- /// registered directly through any of the other generic extensions for the interface.
+ ///
+ /// This method will register all messages corresponding to
+ /// the interfaces being implemented
+ /// by . If none are present, this method will do nothing.
+ ///
+ ///
+ /// This method serves as a runtime fallback registration path.
+ /// Normally, the MVVM Toolkit source generator automatically produces strongly-typed,
+ /// AOT-friendly overloads of this method for each discovered recipient type.
+ /// The C# compiler will prefer those generated methods during overload resolution
+ /// if the exact type is known.
+ ///
+ ///
+ /// This specific extension method (taking an ) is only invoked
+ /// if the recipient was cast to , created dynamically,
+ /// or missed by the source generator.
+ /// It relies on reflection and compiled LINQ expressions to discover and
+ /// register interfaces.
+ /// This incurs a performance overhead on the first invocation for any given type and
+ /// is not safe for Trimming or Native AOT deployment.
+ ///
///
/// Thrown if , or are .
- [RequiresUnreferencedCode(
- "This method requires the generated CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions type not to be removed to use the fast path. " +
- "If this type is removed by the linker, or if the target recipient was created dynamically and was missed by the source generator, a slower fallback " +
- "path using a compiled LINQ expression will be used. This will have more overhead in the first invocation of this method for any given recipient type.")]
- [RequiresDynamicCode("The generic methods to register messages might not be available at runtime.")]
+ [RequiresDynamicCode(
+ "\nThis fallback method uses `MethodInfo.MakeGenericMethod(Type[])` " +
+ "to dynamically construct delegates. " +
+ "This will fail in Native AOT if the specific generic instantiations were not generated. " +
+ "Ensure the exact recipient type implements the `CommunityToolkit.Mvvm.Messaging.IRecipient` interface(s) and " +
+ "is statically known at compile time to use the source-generated, AOT-safe fast path.")]
public static void RegisterAll(this IMessenger messenger, object recipient, TToken token)
where TToken : IEquatable
{
@@ -157,30 +145,15 @@ public static void RegisterAll(this IMessenger messenger, object recipie
ArgumentNullException.For.ThrowIfNull(token);
// We use this method as a callback for the conditional weak table, which will handle
- // thread-safety for us. This first callback will try to find a generated method for the
- // target recipient type, and just invoke it to get the delegate to cache and use later.
- // In this case we also need to create a generic instantiation of the target method first.
- [RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
- [RequiresDynamicCode("The generic methods to register messages might not be available at runtime.")]
- static Action LoadRegistrationMethodsForType(Type recipientType)
- {
- if (recipientType.Assembly.GetType("CommunityToolkit.Mvvm.Messaging.__Internals.__IMessengerExtensions") is Type extensionsType &&
- extensionsType.GetMethod("CreateAllMessagesRegistratorWithToken", new[] { recipientType }) is MethodInfo methodInfo)
- {
- MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(typeof(TToken));
-
- return (Action)genericMethodInfo.Invoke(null, new object?[] { null })!;
- }
-
- return LoadRegistrationMethodsForTypeFallback(recipientType);
- }
-
- // Fallback method when a generated method is not found.
+ // thread-safety for us.
// This method is only invoked once per recipient type and token type, so we're not
// worried about making it super efficient, and we can use the LINQ code for clarity.
// The LINQ codegen bloat is not really important for the same reason.
- [RequiresDynamicCode("The generic methods to register messages might not be available at runtime.")]
- static Action LoadRegistrationMethodsForTypeFallback(Type recipientType)
+ [RequiresDynamicCode(
+ "The generated strongly-typed method to register messages might not be available at runtime. " +
+ "Calls `System.Reflection.MethodInfo.MakeGenericMethod(params Type[])`",
+ Url = "https://learn.microsoft.com/dotnet/core/deploying/native-aot/intrinsic-requiresdynamiccode-apis#methodinfomakegenericmethodtype-method-net-9")]
+ static Action LoadRegistrationMethodsForType(Type recipientType)
{
// Get the collection of validation methods
MethodInfo[] registrationMethods = (
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems
index 8f141adb6..214ef14bb 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems
@@ -12,6 +12,7 @@
+
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.IMessengerRegisterAll.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.IMessengerRegisterAll.cs
new file mode 100644
index 000000000..e4a539dff
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.IMessengerRegisterAll.cs
@@ -0,0 +1,74 @@
+// 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.
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
+
+partial class Test_SourceGeneratorsCodegen
+{
+ private const string TestMessage = /* lang=c#-test */ """
+public class TestMessage
+{
+ public string Content { get; set; } = string.Empty;
+}
+""";
+
+ [TestMethod]
+ public void IMessengerRegisterAll_GenerateCode()
+ {
+ string source = /* lang=c# */ $$"""
+ using CommunityToolkit.Mvvm.Messaging;
+
+ namespace MyApp;
+
+ {{TestMessage}}
+
+ public partial class MyViewModel : IRecipient
+ {
+ public bool IsMessageReceived { get; private set; }
+ public string ReceivedContent { get; private set; } = string.Empty;
+
+ public void Receive(TestMessage message)
+ {
+ IsMessageReceived = true;
+ ReceivedContent = message.Content;
+ }
+ }
+ """;
+
+ string result = /* lang=c# */ """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace CommunityToolkit.Mvvm.Messaging
+ {
+ ///
+ partial class __IMessengerExtensions
+ {
+ ///
+ /// Registers all declared message handlers for a given recipient, using the default channel.
+ ///
+ ///
+ public static void RegisterAll(this global::CommunityToolkit.Mvvm.Messaging.IMessenger messenger, global::MyApp.MyViewModel recipient)
+ {
+ messenger.Register(recipient);
+ }
+
+ ///
+ /// Registers all declared message handlers for a given recipient.
+ ///
+ ///
+ public static void RegisterAll(this global::CommunityToolkit.Mvvm.Messaging.IMessenger messenger, global::MyApp.MyViewModel recipient, TToken token)
+ where TToken : notnull, global::System.IEquatable
+ {
+ messenger.Register(recipient, token);
+ }
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new IMessengerRegisterAllGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+}
diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ArgumentNullException.Messaging.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ArgumentNullException.Messaging.cs
index eed2d8846..32a18793c 100644
--- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ArgumentNullException.Messaging.cs
+++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ArgumentNullException.Messaging.cs
@@ -50,10 +50,10 @@ public void Test_ArgumentNullException_MessengerExtensions(Type type)
Assert(() => messenger.IsRegistered(recipient: null!), "recipient");
Assert(() => ((IMessenger)null!).RegisterAll(new object()), "messenger");
- Assert(() => messenger.RegisterAll(recipient: null!), "recipient");
+ Assert(() => messenger.RegisterAll(recipient: (object)null!), "recipient");
Assert(() => ((IMessenger)null!).RegisterAll(new object(), ""), "messenger");
- Assert(() => messenger.RegisterAll(recipient: null!, ""), "recipient");
+ Assert(() => messenger.RegisterAll(recipient: (object)null!, ""), "recipient");
Assert(() => messenger.RegisterAll(new object(), token: null!), "token");
Assert(() => ((IMessenger)null!).Register(new Recipient()), "messenger");
diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_IRecipientGenerator.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_IRecipientGenerator.cs
index 782e41450..8c7b4e119 100644
--- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_IRecipientGenerator.cs
+++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_IRecipientGenerator.cs
@@ -24,9 +24,7 @@ public void Test_IRecipientGenerator_GeneratedRegistration()
MessageA messageA = new();
MessageB messageB = new();
- Action registrator = Messaging.__Internals.__IMessengerExtensions.CreateAllMessagesRegistratorWithToken(recipient);
-
- registrator(messenger, recipient, 42);
+ messenger.RegisterAll(recipient, 42);
Assert.IsTrue(messenger.IsRegistered(recipient, 42));
Assert.IsTrue(messenger.IsRegistered(recipient, 42));
@@ -51,16 +49,16 @@ public void Test_IRecipientGenerator_TypeWithMultipleClassDeclarations()
RecipientWithMultipleClassDeclarations recipient = new();
// This test really just needs to verify this compiles and executes normally
- _ = Messaging.__Internals.__IMessengerExtensions.CreateAllMessagesRegistratorWithToken(recipient);
+ WeakReferenceMessenger.Default.RegisterAll(recipient, 5);
}
[TestMethod]
public void Test_IRecipientGenerator_AbstractTypesDoNotTriggerCodeGeneration()
{
- MethodInfo? createAllPropertiesValidatorMethod = typeof(Messaging.__Internals.__IMessengerExtensions)
+ MethodInfo? createAllPropertiesValidatorMethod = typeof(Messaging.__IMessengerExtensions)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
- .Where(static m => m.Name == "CreateAllMessagesRegistratorWithToken")
- .Where(static m => m.GetParameters() is { Length: 1 } parameters && parameters[0].ParameterType == typeof(AbstractModelWithValidatablePropertyIRecipientInterfaces))
+ .Where(static m => m.Name == "RegisterAll")
+ .Where(static m => m.GetParameters() is { Length: 3 } parameters && parameters[1].ParameterType == typeof(AbstractModelWithValidatablePropertyIRecipientInterfaces))
.FirstOrDefault();
// We need to validate that no methods are generated for abstract types, so we just check this method doesn't exist
diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs
index 28135603c..7d46c3774 100644
--- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs
+++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs
@@ -97,13 +97,13 @@ public void Test_IRecipient_VerifyTrimmingAnnotation()
{
#if NET6_0_OR_GREATER
System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute? attribute =
- typeof(Messaging.__Internals.__IMessengerExtensions)
+ typeof(Messaging.__IMessengerExtensions)
.GetCustomAttribute();
Assert.IsNotNull(attribute);
Assert.AreEqual(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods, attribute.MemberTypes);
#else
- IEnumerable attributes = typeof(Messaging.__Internals.__IMessengerExtensions).GetCustomAttributes();
+ IEnumerable attributes = typeof(Messaging.__IMessengerExtensions).GetCustomAttributes();
Assert.IsFalse(attributes.Any(static a => a.GetType().Name is "DynamicallyAccessedMembersAttribute"));
#endif