本篇定位
StrongReferenceMessenger 使用强引用,数据结构完全不同。
1. 何时用 StrongReferenceMessenger?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class EditDialogViewModel { private readonly StrongReferenceMessenger _messenger = new();
public EditDialogViewModel() { _messenger.Register<SaveMessage>(this, Handler); }
public void OnClose() { _messenger.UnregisterAll(this); } }
public class GameHudViewModel { private static readonly StrongReferenceMessenger Messenger = new();
public void UpdateHealth(int hp) { Messenger.Send(new HealthUpdateMessage(hp)); } }
|
2. 内部数据结构
1 2 3 4 5 6 7 8
| public sealed class StrongReferenceMessenger : IMessenger { private readonly Dictionary2<Recipient, HashSet<IMapping>> recipientsMap = new();
private readonly Dictionary2<Type2, IMapping> typesMap = new(); }
|
关键差异: 这里没有 ConditionalWeakTable2,而是用 Recipient 结构体作强引用。
3. Recipient 结构体
1 2 3 4 5 6 7 8 9 10 11 12
| private readonly struct Recipient : IEquatable<Recipient> { public readonly object Target;
public Recipient(object target) => Target = target;
public bool Equals(Recipient other) => ReferenceEquals(Target, other.Target);
public override int GetHashCode() => RuntimeHelpers.GetHashCode(Target); }
|
4. Mapping 与 Mapping
1 2 3 4 5 6 7
| private sealed class Mapping : Dictionary2<Recipient, object?>, IMapping { }
private sealed class Mapping<TToken> : Dictionary2<Recipient, Dictionary2<TToken, object?>>, IMapping where TToken : IEquatable<TToken> { }
|
数据结构图解:
1 2 3 4 5 6 7
| typesMap: Type2(NavMsg, Unit) → Mapping ─── Recipient_A → null typesMap: Type2(StatusMsg, string) → Mapping<string> ─── Recipient_B → Dictionary2 ├── "Errors" → dispatcher └── "Info" → dispatcher
recipientsMap: Recipient_A → HashSet{ Mapping<NavMsg> } Recipient_B → HashSet{ Mapping<string><StatusMsg> }
|
双重记录的好处:UnregisterAll(recipient) 只需查 recipientsMap,O(1) 找到该接收者的所有映射,逐个清理。
5. Register 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| private void Register<TMessage, TToken>( object recipient, TToken token, MessageHandlerDispatcher? dispatcher) { lock (this.recipientsMap) { Recipient key = new(recipient); IMapping mapping;
if (typeof(TToken) == typeof(Unit)) { Mapping underlyingMapping = GetOrAddMapping<TMessage>(); ref object? handler = ref underlyingMapping.GetOrAddValueRef(key); if (handler is not null) ThrowDuplicate(); handler = dispatcher; mapping = underlyingMapping; } else { Mapping<TToken> underlyingMapping = GetOrAddMapping<TMessage, TToken>(); ref var map = ref underlyingMapping.GetOrAddValueRef(key); map ??= new Dictionary2<TToken, object?>(); ref object? handler = ref map.GetOrAddValueRef(token); if (handler is not null) ThrowDuplicate(); handler = dispatcher; mapping = underlyingMapping; }
ref HashSet<IMapping>? set = ref this.recipientsMap.GetOrAddValueRef(key); set ??= new HashSet<IMapping>(); set.Add(mapping); } }
|
6. Send 源码(ArrayPool 版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public TMessage Send<TMessage, TToken>(TMessage message, TToken token) { object?[] rentedArray; int i = 0;
lock (this.recipientsMap) { rentedArray = ArrayPool<object?>.Shared.Rent(2 * totalHandlersCount);
}
try { WeakReferenceMessenger.SendAll(pairs, i, message); } finally { Array.Clear(rentedArray, 0, 2 * i); ArrayPool<object?>.Shared.Return(rentedArray); }
return message; }
|
7. Cleanup 是空方法
1 2 3 4 5
| void IMessenger.Cleanup() { }
|
对比 WeakReferenceMessenger 需要 Gen2 GC 回调清理死条目。
8. 性能对比
| 操作 |
WeakReferenceMessenger |
StrongReferenceMessenger |
| 接收者引用 |
弱引用 |
强引用(需手动 Unregister) |
| Send 数组 |
ArrayPoolBufferWriter |
ArrayPool<object> |
| Cleanup |
Gen2 GC 自动 |
无(同步清理) |
| 哈希 |
依赖对象 GetHashCode |
RuntimeHelpers.GetHashCode |
下一篇预告:RelayCommand 源码分析 — 类型安全的泛型命令参数处理