跳到主要内容
知仓学习社ZHICANG

mvvm-toolkit-messenger

CommunityToolkit.Mvvm Messenger pub/sub for decoupled communication between ViewModels (or any objects). Covers WeakReferenceMessenger vs StrongRefe…

不碰外部(只输出文字)无严重或高危命中github/awesome-copilot

它会碰到什么

扫了多少2 个文本文件,16 KB
它会碰到什么不碰外部(只输出文字)
命中总数0 处
命中统计严重 0 · 高 0 · 中 0 · 低 0

这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。

技能内容

CommunityToolkit.Mvvm Messenger

Pub/sub messaging for ViewModels (or any objects) without forcing a shared

reference graph. Part of CommunityToolkit.Mvvm 8.x.

> TL;DR. Default to WeakReferenceMessenger.Default. Register handlers

> with the (recipient, message) lambda and the static modifier so you

> never capture this. Inherit from ObservableRecipient and toggle

> IsActive at activation/deactivation to get automatic register/unregister.


When to use this skill

  • Two or more ViewModels need to react to an event (login, theme change,

save, navigation) without holding references to each other

  • A ViewModel needs to ask another VM for a value (request/reply)
  • You're scoping events to a sub-system or window with channel tokens
  • Diagnosing "my handler never fires" or weak-reference recipient lifetime

problems

For source generators, base classes, and commands see the mvvm-toolkit

skill. For DI wiring (registering an IMessenger instance), see

mvvm-toolkit-di.


Choose an implementation

| Type | When |

|------|------|

| WeakReferenceMessenger.Default | Default. Recipients held weakly — eligible for GC even while registered. Internal trimming runs during full GCs; no manual Cleanup() needed. |

| StrongReferenceMessenger.Default | Profiler shows the messenger is hot and allocation matters. Recipients are pinned until you Unregister. Forgetting unregistration leaks them. |

| Custom IMessenger instance | Per-window/per-scope (e.g., one messenger per app window). Construct directly, inject via DI. |

ObservableRecipient's parameterless constructor uses

WeakReferenceMessenger.Default. Pass a different IMessenger to its

constructor to override.


Define a message

The toolkit ships base classes; any class works.

using CommunityToolkit.Mvvm.Messaging.Messages;

// Single-payload broadcast
public sealed class LoggedInUserChangedMessage(User user)
    : ValueChangedMessage<User>(user);

// Custom shape (records are great for this)
public sealed record ThemeChangedMessage(AppTheme NewTheme);

// Empty signal
public sealed record RefreshRequestedMessage;

Register a recipient

Lambda style (recommended)

WeakReferenceMessenger.Default.Register<MyViewModel, ThemeChangedMessage>(
    this,
    static (recipient, message) => recipient.OnThemeChanged(message.NewTheme));

The static modifier prevents accidental closure allocation and keeps

this out of the lambda — use the recipient parameter instead.

IRecipient<TMessage> interface style

public sealed class MyViewModel : ObservableRecipient,
    IRecipient<ThemeChangedMessage>,
    IRecipient<RefreshRequestedMessage>
{
    public void Receive(ThemeChangedMessage message) { /* ... */ }
    public void Receive(RefreshRequestedMessage message) { /* ... */ }
}

ObservableRecipient.OnActivated() calls Messenger.RegisterAll(this),

which subscribes every IRecipient<T> interface implemented by the type.

If you're not using ObservableRecipient, register manually:

WeakReferenceMessenger.Default.RegisterAll(this);

Send a message

WeakReferenceMessenger.Default.Send(new ThemeChangedMessage(AppTheme.Dark));

// Empty payloads use the parameterless overload:
WeakReferenceMessenger.Default.Send<RefreshRequestedMessage>();

Channels (tokens)

Scope messages to a sub-system or window with a token (any equatable

value — int, string, Guid):

const int LeftPaneChannel = 1;

WeakReferenceMessenger.Default.Register<MyViewModel, RefreshRequestedMessage, int>(
    this, LeftPaneChannel,
    static (r, _) => r.RefreshLeft());

WeakReferenceMessenger.Default.Send(new RefreshRequestedMessage(), LeftPaneChannel);

Messages sent without a token use the default shared channel — they are

not delivered to channel-scoped recipients.


Request / reply

For ask-style scenarios where a recipient provides a value back to the

sender, use the RequestMessage<T> family.

Sync request

public sealed class CurrentUserRequest : RequestMessage<User> { }

WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(
    this,
    static (r, m) => m.Reply(r.CurrentUser));

User user = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();

The implicit conversion from CurrentUserRequest to User throws if no

recipient called Reply. Capture the message to check first:

var request = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();
if (request.HasReceivedResponse)
    User user = request.Response;

Async request

public sealed class CurrentUserRequest : AsyncRequestMessage<User> { }

WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(
    this,
    static (r, m) => m.Reply(r.GetCurrentUserAsync()));

User user = await WeakReferenceMessenger.Default.Send<CurrentUserRequest>();

Collection requests (fan-in)

CollectionRequestMessage<T> and AsyncCollectionRequestMessage<T> collect

a Reply from every responding recipient:

public sealed class OpenDocumentsRequest : CollectionRequestMessage<Document> { }

var docs = WeakReferenceMessenger.Default.Send<OpenDocumentsRequest>();
foreach (Document doc in docs) { /* ... */ }

Lifecycle

Even with WeakReferenceMessenger, unregister explicitly when a recipient

is being torn down — it trims dead entries and improves performance:

WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage>(this);
WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage, int>(this, LeftPaneChannel);
WeakReferenceMessenger.Default.UnregisterAll(this);

ObservableRecipient.OnDeactivated() does this automatically when

IsActive flips to false. Set it from your activation hook:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    ViewModel.IsActive = true;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    ViewModel.IsActive = false;
    base.OnNavigatedFrom(e);
}

Common pitfalls

  1. Capturing this in the lambda. (r, m) => OnX(m) implicitly

captures this; allocates a closure and confuses lifetime. Always use

(r, m) => r.OnX(m) with static.

  1. Strong-ref recipients without Unregister. With

StrongReferenceMessenger, recipients (and their entire object graph)

stay pinned forever. Either inherit from ObservableRecipient

(auto-unregisters in OnDeactivated) or call UnregisterAll(this).

  1. Inherited message types. A handler registered for BaseMessage is

not invoked for DerivedMessage : BaseMessage. Register each

concrete type.

  1. Wrong messenger instance. Sending via WeakReferenceMessenger.Default

and registering via an injected per-window messenger means the message

never arrives. Use the same IMessenger everywhere (typically inject

it via ObservableRecipient(messenger)).

  1. OnActivated never runs. ObservableRecipient only registers

IRecipient<T> handlers when IsActive flips from false to true.

  1. Cross-thread updates. The messenger is thread-agnostic. If a

handler updates UI, marshal manually

(DispatcherQueue.TryEnqueue / Dispatcher.BeginInvoke).


Multiple messengers (per-window scoping)

services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default); // app-wide
services.AddScoped<WindowScopedMessenger>();                       // per-window

Inject the appropriate IMessenger into the ViewModel constructor:

public sealed partial class WindowViewModel(IMessenger messenger)
    : ObservableRecipient(messenger) { }

This isolates broadcasts to a single window — useful for multi-window

desktop apps (WinUI 3, WPF, MAUI desktop, Avalonia).


References

| Topic | File |

|-------|------|

| Full deep dive (more channel/lifecycle examples, diagnostics) | [references/messenger-patterns.md](references/messenger-patterns.md) |

External:

  • Messenger docs: <https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger>
  • WeakReferenceMessenger API: <https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger>
  • Source: <https://github.com/CommunityToolkit/dotnet>

想直接用这个技能?

本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。