// 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 System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.Mvvm.UnitTests; [TestClass] public partial class Test_Messenger { [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_UnregisterRecipientWithMessageType(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Unregister(recipient); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_UnregisterRecipientWithMessageTypeAndToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Unregister(recipient, nameof(MessageA)); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_UnregisterRecipientWithToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.UnregisterAll(recipient, nameof(MessageA)); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_UnregisterRecipientWithRecipient(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.UnregisterAll(recipient); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageType(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Register(recipient, (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient)); messenger.Unregister(recipient); Assert.IsFalse(messenger.IsRegistered(recipient)); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageTypeAndToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Register(recipient, nameof(MessageA), (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient, nameof(MessageA))); messenger.Unregister(recipient, nameof(MessageA)); Assert.IsFalse(messenger.IsRegistered(recipient, nameof(MessageA))); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageTypeAndMultipleTokens(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); Assert.IsFalse(messenger.IsRegistered(recipient, 1)); Assert.IsFalse(messenger.IsRegistered(recipient, 2)); Assert.IsFalse(messenger.IsRegistered(recipient, "a")); Assert.IsFalse(messenger.IsRegistered(recipient, "b")); messenger.Register(recipient, 1, (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient, 1)); Assert.IsFalse(messenger.IsRegistered(recipient, 2)); Assert.IsFalse(messenger.IsRegistered(recipient, "a")); Assert.IsFalse(messenger.IsRegistered(recipient, "b")); messenger.Register(recipient, 2, (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient, 1)); Assert.IsTrue(messenger.IsRegistered(recipient, 2)); Assert.IsFalse(messenger.IsRegistered(recipient, "a")); Assert.IsFalse(messenger.IsRegistered(recipient, "b")); messenger.Register(recipient, "a", (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient, 1)); Assert.IsTrue(messenger.IsRegistered(recipient, 2)); Assert.IsTrue(messenger.IsRegistered(recipient, "a")); Assert.IsFalse(messenger.IsRegistered(recipient, "b")); messenger.Register(recipient, "b", (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient, 1)); Assert.IsTrue(messenger.IsRegistered(recipient, 2)); Assert.IsTrue(messenger.IsRegistered(recipient, "a")); Assert.IsTrue(messenger.IsRegistered(recipient, "b")); messenger.Unregister(recipient, 1); Assert.IsFalse(messenger.IsRegistered(recipient, 1)); Assert.IsTrue(messenger.IsRegistered(recipient, 2)); Assert.IsTrue(messenger.IsRegistered(recipient, "a")); Assert.IsTrue(messenger.IsRegistered(recipient, "b")); messenger.Unregister(recipient, 2); Assert.IsFalse(messenger.IsRegistered(recipient, 1)); Assert.IsFalse(messenger.IsRegistered(recipient, 2)); Assert.IsTrue(messenger.IsRegistered(recipient, "a")); Assert.IsTrue(messenger.IsRegistered(recipient, "b")); messenger.Unregister(recipient, "a"); Assert.IsFalse(messenger.IsRegistered(recipient, 1)); Assert.IsFalse(messenger.IsRegistered(recipient, 2)); Assert.IsFalse(messenger.IsRegistered(recipient, "a")); Assert.IsTrue(messenger.IsRegistered(recipient, "b")); messenger.Unregister(recipient, "b"); Assert.IsFalse(messenger.IsRegistered(recipient, 1)); Assert.IsFalse(messenger.IsRegistered(recipient, 2)); Assert.IsFalse(messenger.IsRegistered(recipient, "a")); Assert.IsFalse(messenger.IsRegistered(recipient, "b")); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_RegisterAndUnregisterRecipientWithToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Register(recipient, nameof(MessageA), (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient, nameof(MessageA))); messenger.UnregisterAll(recipient, nameof(MessageA)); Assert.IsFalse(messenger.IsRegistered(recipient, nameof(MessageA))); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_RegisterAndUnregisterRecipientWithRecipient(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Register(recipient, nameof(MessageA), (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient, nameof(MessageA))); messenger.UnregisterAll(recipient); Assert.IsFalse(messenger.IsRegistered(recipient, nameof(MessageA))); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithNoToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object a = new(); Assert.IsFalse(messenger.IsRegistered(a)); object? recipient = null; string? result = null; messenger.Register(a, (r, m) => { recipient = r; result = m.Text; }); Assert.IsTrue(messenger.IsRegistered(a)); _ = messenger.Send(new MessageA { Text = nameof(MessageA) }); Assert.AreSame(recipient, a); Assert.AreEqual(result, nameof(MessageA)); messenger.Unregister(a); Assert.IsFalse(messenger.IsRegistered(a)); recipient = null; result = null; _ = messenger.Send(new MessageA { Text = nameof(MessageA) }); Assert.IsNull(recipient); Assert.IsNull(result); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_IsRegistered_Register_Send_UnregisterRecipient_WithNoToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object a = new(); Assert.IsFalse(messenger.IsRegistered(a)); string? result = null; messenger.Register(a, (r, m) => result = m.Text); Assert.IsTrue(messenger.IsRegistered(a)); _ = messenger.Send(new MessageA { Text = nameof(MessageA) }); Assert.AreEqual(result, nameof(MessageA)); messenger.UnregisterAll(a); Assert.IsFalse(messenger.IsRegistered(a)); result = null; _ = messenger.Send(new MessageA { Text = nameof(MessageA) }); Assert.IsNull(result); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object a = new(); Assert.IsFalse(messenger.IsRegistered(a)); string? result = null; messenger.Register(a, nameof(MessageA), (r, m) => result = m.Text); Assert.IsTrue(messenger.IsRegistered(a, nameof(MessageA))); _ = messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); Assert.AreEqual(result, nameof(MessageA)); messenger.Unregister(a, nameof(MessageA)); Assert.IsFalse(messenger.IsRegistered(a, nameof(MessageA))); result = null; _ = messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); Assert.IsNull(result); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_DuplicateRegistrationWithMessageType(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Register(recipient, (r, m) => { }); _ = Assert.ThrowsException(() => { messenger.Register(recipient, (r, m) => { }); }); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_DuplicateRegistrationWithMessageTypeAndToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient = new(); messenger.Register(recipient, nameof(MessageA), (r, m) => { }); _ = Assert.ThrowsException(() => { messenger.Register(recipient, nameof(MessageA), (r, m) => { }); }); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_IRecipient_NoMessages(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithNoMessages? recipient = new(); messenger.RegisterAll(recipient); // We just need to verify we got here with no errors, this // recipient has no declared handlers so there's nothing to do } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_IRecipient_SomeMessages_NoToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithSomeMessages? recipient = new(); messenger.RegisterAll(recipient); Assert.IsTrue(messenger.IsRegistered(recipient)); Assert.IsTrue(messenger.IsRegistered(recipient)); Assert.AreEqual(recipient.As, 0); Assert.AreEqual(recipient.Bs, 0); _ = messenger.Send(); Assert.AreEqual(recipient.As, 1); Assert.AreEqual(recipient.Bs, 0); _ = messenger.Send(); Assert.AreEqual(recipient.As, 1); Assert.AreEqual(recipient.Bs, 1); messenger.UnregisterAll(recipient); Assert.IsFalse(messenger.IsRegistered(recipient)); Assert.IsFalse(messenger.IsRegistered(recipient)); } // See https://github.com/CommunityToolkit/dotnet/issues/198 [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_IRecipient_SomeMessages_WithGenericTypeParameters(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; GenericRecipient recipient = new(); messenger.RegisterAll(recipient); Assert.IsTrue(messenger.IsRegistered.Message>(recipient)); Assert.IsTrue(messenger.IsRegistered>(recipient)); Assert.IsTrue(messenger.IsRegistered(recipient)); Assert.IsNull(recipient.ReceivedMessage1); Assert.IsNull(recipient.ReceivedMessage2); Assert.IsNull(recipient.ReceivedMessage3); GenericRecipient.Message message1 = messenger.Send(new GenericRecipient.Message(new List { "Hello world!" })); ValueChangedMessage message2 = messenger.Send(new ValueChangedMessage("Hello")); MessageA message3 = messenger.Send(); Assert.AreSame(recipient.ReceivedMessage1, message1); Assert.AreSame(recipient.ReceivedMessage2, message2); Assert.AreSame(recipient.ReceivedMessage3, message3); messenger.UnregisterAll(recipient); Assert.IsFalse(messenger.IsRegistered.Message>(recipient)); Assert.IsFalse(messenger.IsRegistered>(recipient)); Assert.IsFalse(messenger.IsRegistered(recipient)); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_IRecipient_SomeMessages_WithToken(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithSomeMessages? recipient = new(); string? token = nameof(Test_Messenger_IRecipient_SomeMessages_WithToken); messenger.RegisterAll(recipient, token); Assert.IsTrue(messenger.IsRegistered(recipient, token)); Assert.IsTrue(messenger.IsRegistered(recipient, token)); Assert.IsFalse(messenger.IsRegistered(recipient)); Assert.IsFalse(messenger.IsRegistered(recipient)); Assert.AreEqual(recipient.As, 0); Assert.AreEqual(recipient.Bs, 0); _ = messenger.Send(token); Assert.AreEqual(recipient.As, 0); Assert.AreEqual(recipient.Bs, 1); _ = messenger.Send(token); Assert.AreEqual(recipient.As, 1); Assert.AreEqual(recipient.Bs, 1); messenger.UnregisterAll(recipient, token); Assert.IsFalse(messenger.IsRegistered(recipient)); Assert.IsFalse(messenger.IsRegistered(recipient)); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_RegisterWithTypeParameter(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithNoMessages? recipient = new() { Number = 42 }; int number = 0; messenger.Register(recipient, (r, m) => number = r.Number); _ = messenger.Send(); Assert.AreEqual(number, 42); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger), false)] [DataRow(typeof(WeakReferenceMessenger), true)] public void Test_Messenger_Collect_Test(Type type, bool isWeak) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; WeakReference weakRecipient; void Test() { RecipientWithNoMessages? recipient = new() { Number = 42 }; weakRecipient = new WeakReference(recipient); messenger.Register(recipient, (r, m) => { }); Assert.IsTrue(messenger.IsRegistered(recipient)); Assert.IsTrue(weakRecipient.IsAlive); GC.KeepAlive(recipient); } Test(); GC.Collect(); Assert.AreEqual(!isWeak, weakRecipient.IsAlive); GC.KeepAlive(messenger); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_Reset(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithSomeMessages? recipient = new(); messenger.RegisterAll(recipient); Assert.IsTrue(messenger.IsRegistered(recipient)); Assert.IsTrue(messenger.IsRegistered(recipient)); messenger.Reset(); Assert.IsFalse(messenger.IsRegistered(recipient)); Assert.IsFalse(messenger.IsRegistered(recipient)); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_Default(Type type) { PropertyInfo defaultInfo = type.GetProperty("Default")!; object? default1 = defaultInfo!.GetValue(null); object? default2 = defaultInfo!.GetValue(null); Assert.IsNotNull(default1); Assert.IsNotNull(default2); Assert.AreSame(default1, default2); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_Cleanup(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithSomeMessages? recipient = new(); messenger.Register(recipient); Assert.IsTrue(messenger.IsRegistered(recipient)); void Test() { RecipientWithSomeMessages? recipient2 = new(); messenger.Register(recipient2); Assert.IsTrue(messenger.IsRegistered(recipient2)); GC.KeepAlive(recipient2); } Test(); GC.Collect(); // Here we just check that calling Cleanup doesn't alter the state // of the messenger. This method shouldn't really do anything visible // to consumers, it's just a way for messengers to compact their data. messenger.Cleanup(); Assert.IsTrue(messenger.IsRegistered(recipient)); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_ManyRecipients(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; void Test() { RecipientWithSomeMessages?[] recipients = Enumerable.Range(0, 512).Select(_ => new RecipientWithSomeMessages()).ToArray(); foreach (RecipientWithSomeMessages? recipient in recipients) { messenger.RegisterAll(recipient!); } foreach (RecipientWithSomeMessages? recipient in recipients) { Assert.IsTrue(messenger.IsRegistered(recipient!)); Assert.IsTrue(messenger.IsRegistered(recipient!)); } _ = messenger.Send(); _ = messenger.Send(); _ = messenger.Send(); foreach (RecipientWithSomeMessages? recipient in recipients) { Assert.AreEqual(recipient!.As, 1); Assert.AreEqual(recipient!.Bs, 2); } foreach (ref RecipientWithSomeMessages? recipient in recipients.AsSpan()) { recipient = null; } } Test(); GC.Collect(); // Just invoke a final cleanup to improve coverage, this is unrelated to this test in particular messenger.Cleanup(); } // See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/4081 [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_RegisterMultiple_UnregisterSingle(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; object? recipient1 = new(); object? recipient2 = new(); int handler1CalledCount = 0; int handler2CalledCount = 0; messenger.Register(recipient1, (r, m) => { handler1CalledCount++; }); messenger.Register(recipient2, (r, m) => { handler2CalledCount++; }); messenger.UnregisterAll(recipient2); _ = messenger.Send(new MessageA()); Assert.AreEqual(1, handler1CalledCount); Assert.AreEqual(0, handler2CalledCount); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_ConcurrentOperations_DefaultChannel_SeparateRecipients(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithConcurrency[] recipients = Enumerable.Range(0, 1024).Select(static _ => new RecipientWithConcurrency()).ToArray(); DateTime end = DateTime.Now.AddSeconds(5); _ = Parallel.For(0, recipients.Length, i => { RecipientWithConcurrency r = recipients[i]; Random random = new(i); while (DateTime.Now < end) { Assert.IsFalse(messenger.IsRegistered(r)); Assert.IsFalse(messenger.IsRegistered(r)); // Here we randomize the way messages are subscribed. This ensures that we're testing both the normal // registration with a delegate, as well as the fast path within WeakReferenceMessenger that is enabled // when IRecipient is used. Since broadcasts are done in parallel here, it will happen that // some messages are broadcast when multiple recipients are active through both types of registration. // This test verifies that the messengers work fine when this is the case (and not just when only one // of the two method is used, as mixing them up as necessary is a perfectly supported scenario as well). switch (random.Next(0, 3)) { case 0: messenger.Register(r, static (r, m) => r.Receive(m)); messenger.Register(r, static (r, m) => r.Receive(m)); break; case 1: messenger.Register(r); messenger.Register(r); break; default: messenger.RegisterAll(r); break; } Assert.IsTrue(messenger.IsRegistered(r)); Assert.IsTrue(messenger.IsRegistered(r)); int a = r.As; int b = r.Bs; _ = messenger.Send(); // We can't just check that the count has been increased by 1, as there may be other concurrent broadcasts Assert.IsTrue(r.As > a); _ = messenger.Send(); Assert.IsTrue(r.Bs > b); switch (random.Next(0, 2)) { case 0: messenger.Unregister(r); messenger.Unregister(r); break; default: messenger.UnregisterAll(r); break; } } }); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_ConcurrentOperations_DefaultChannel_SharedRecipients(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithConcurrency[] recipients = Enumerable.Range(0, 512).Select(static _ => new RecipientWithConcurrency()).ToArray(); DateTime end = DateTime.Now.AddSeconds(5); _ = Parallel.For(0, recipients.Length * 2, i => { RecipientWithConcurrency r = recipients[i / 2]; Random random = new(i); while (DateTime.Now < end) { if (i % 2 == 0) { Assert.IsFalse(messenger.IsRegistered(r)); switch (random.Next(0, 2)) { case 0: messenger.Register(r, static (r, m) => r.Receive(m)); break; default: messenger.Register(r); break; } Assert.IsTrue(messenger.IsRegistered(r)); int a = r.As; _ = messenger.Send(); Assert.IsTrue(r.As > a); messenger.Unregister(r); } else { Assert.IsFalse(messenger.IsRegistered(r)); switch (random.Next(0, 2)) { case 0: messenger.Register(r, static (r, m) => r.Receive(m)); break; default: messenger.Register(r); break; } Assert.IsTrue(messenger.IsRegistered(r)); int b = r.Bs; _ = messenger.Send(); Assert.IsTrue(r.Bs > b); messenger.Unregister(r); } } }); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_ConcurrentOperations_WithToken_SeparateRecipients(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithConcurrency[] recipients = Enumerable.Range(0, 1024).Select(static _ => new RecipientWithConcurrency()).ToArray(); string[] tokens = Enumerable.Range(0, 16).Select(static i => i.ToString()).ToArray(); DateTime end = DateTime.Now.AddSeconds(5); _ = Parallel.For(0, recipients.Length, i => { RecipientWithConcurrency r = recipients[i]; string token = tokens[i % tokens.Length]; Random random = new(i); while (DateTime.Now < end) { Assert.IsFalse(messenger.IsRegistered(r, token)); Assert.IsFalse(messenger.IsRegistered(r, token)); switch (random.Next(0, 3)) { case 0: messenger.Register(r, token, static (r, m) => r.Receive(m)); messenger.Register(r, token, static (r, m) => r.Receive(m)); break; case 1: messenger.Register(r, token); messenger.Register(r, token); break; default: messenger.RegisterAll(r, token); break; } Assert.IsTrue(messenger.IsRegistered(r, token)); Assert.IsTrue(messenger.IsRegistered(r, token)); int a = r.As; int b = r.Bs; _ = messenger.Send(token); Assert.IsTrue(r.As > a); _ = messenger.Send(token); Assert.IsTrue(r.Bs > b); switch (random.Next(0, 3)) { case 0: messenger.Unregister(r, token); messenger.Unregister(r, token); break; case 1: messenger.UnregisterAll(r, token); break; default: messenger.UnregisterAll(r); break; } } }); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_ConcurrentOperations_WithToken_SharedRecipients(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithConcurrency[] recipients = Enumerable.Range(0, 512).Select(static _ => new RecipientWithConcurrency()).ToArray(); string[] tokens = Enumerable.Range(0, 16).Select(static i => i.ToString()).ToArray(); DateTime end = DateTime.Now.AddSeconds(5); _ = Parallel.For(0, recipients.Length * 2, i => { RecipientWithConcurrency r = recipients[i / 2]; string token = tokens[i % tokens.Length]; Random random = new(i); while (DateTime.Now < end) { if (i % 2 == 0) { Assert.IsFalse(messenger.IsRegistered(r, token)); switch (random.Next(0, 2)) { case 0: messenger.Register(r, token, static (r, m) => r.Receive(m)); break; default: messenger.Register(r, token); break; } Assert.IsTrue(messenger.IsRegistered(r, token)); int a = r.As; _ = messenger.Send(token); Assert.IsTrue(r.As > a); messenger.Unregister(r, token); } else { Assert.IsFalse(messenger.IsRegistered(r, token)); switch (random.Next(0, 2)) { case 0: messenger.Register(r, token, static (r, m) => r.Receive(m)); break; default: messenger.Register(r, token); break; } Assert.IsTrue(messenger.IsRegistered(r, token)); int b = r.Bs; _ = messenger.Send(token); Assert.IsTrue(r.Bs > b); messenger.Unregister(r, token); } } }); } [TestMethod] [DataRow(typeof(StrongReferenceMessenger))] [DataRow(typeof(WeakReferenceMessenger))] public void Test_Messenger_ConcurrentOperations_Combined_SeparateRecipients(Type type) { IMessenger? messenger = (IMessenger)Activator.CreateInstance(type)!; RecipientWithConcurrency[] recipients = Enumerable.Range(0, 1024).Select(static _ => new RecipientWithConcurrency()).ToArray(); string[] wordTokens = Enumerable.Range(0, 16).Select(static i => i.ToString()).ToArray(); int[] numberTokens = Enumerable.Range(0, 16).ToArray(); DateTime end = DateTime.Now.AddSeconds(5); _ = Parallel.For(0, recipients.Length, i => { RecipientWithConcurrency r = recipients[i]; string wordToken = wordTokens[i % wordTokens.Length]; int numberToken = numberTokens[i % numberTokens.Length]; Random random = new(i); while (DateTime.Now < end) { Assert.IsFalse(messenger.IsRegistered(r, wordToken)); Assert.IsFalse(messenger.IsRegistered(r, wordToken)); Assert.IsFalse(messenger.IsRegistered(r, numberToken)); Assert.IsFalse(messenger.IsRegistered(r, numberToken)); Assert.IsFalse(messenger.IsRegistered(r)); Assert.IsFalse(messenger.IsRegistered(r)); int choice = random.Next(0, 9); switch (choice) { case 0: messenger.Register(r, wordToken, static (r, m) => r.Receive(m)); messenger.Register(r, wordToken, static (r, m) => r.Receive(m)); Assert.IsTrue(messenger.IsRegistered(r, wordToken)); Assert.IsTrue(messenger.IsRegistered(r, wordToken)); break; case 1: messenger.Register(r, numberToken, static (r, m) => r.Receive(m)); messenger.Register(r, numberToken, static (r, m) => r.Receive(m)); Assert.IsTrue(messenger.IsRegistered(r, numberToken)); Assert.IsTrue(messenger.IsRegistered(r, numberToken)); break; case 2: messenger.Register(r, static (r, m) => r.Receive(m)); messenger.Register(r, static (r, m) => r.Receive(m)); Assert.IsTrue(messenger.IsRegistered(r)); Assert.IsTrue(messenger.IsRegistered(r)); break; case 3: messenger.Register(r, wordToken); messenger.Register(r, wordToken); Assert.IsTrue(messenger.IsRegistered(r, wordToken)); Assert.IsTrue(messenger.IsRegistered(r, wordToken)); break; case 4: messenger.Register(r, numberToken); messenger.Register(r, numberToken); Assert.IsTrue(messenger.IsRegistered(r, numberToken)); Assert.IsTrue(messenger.IsRegistered(r, numberToken)); break; case 5: messenger.Register(r); messenger.Register(r); Assert.IsTrue(messenger.IsRegistered(r)); Assert.IsTrue(messenger.IsRegistered(r)); break; case 6: messenger.RegisterAll(r, wordToken); Assert.IsTrue(messenger.IsRegistered(r, wordToken)); Assert.IsTrue(messenger.IsRegistered(r, wordToken)); break; case 7: messenger.RegisterAll(r, numberToken); Assert.IsTrue(messenger.IsRegistered(r, numberToken)); Assert.IsTrue(messenger.IsRegistered(r, numberToken)); break; default: messenger.RegisterAll(r); Assert.IsTrue(messenger.IsRegistered(r)); Assert.IsTrue(messenger.IsRegistered(r)); break; } int a = r.As; int b = r.Bs; if (choice is 0 or 3 or 6) { _ = messenger.Send(wordToken); _ = messenger.Send(wordToken); messenger.UnregisterAll(r, wordToken); } else if (choice is 1 or 4 or 7) { _ = messenger.Send(numberToken); _ = messenger.Send(numberToken); messenger.UnregisterAll(r, numberToken); } else { _ = messenger.Send(); _ = messenger.Send(); messenger.UnregisterAll(r); } Assert.IsTrue(r.As > a); Assert.IsTrue(r.Bs > b); } }); } public sealed class RecipientWithNoMessages { public int Number { get; set; } } public sealed class RecipientWithSomeMessages : IRecipient, IRecipient, ICloneable { public int As { get; private set; } public int Bs { get; private set; } public void Receive(MessageA message) { As++; } public void Receive(MessageB message) { Bs++; } // We also add the ICloneable interface to test that the message // interfaces are all handled correctly even when interleaved // by other unrelated interfaces in the type declaration. public object Clone() => throw new NotImplementedException(); } public sealed class RecipientWithConcurrency : IRecipient, IRecipient { private volatile int a; private volatile int b; public int As => this.a; public int Bs => this.b; public void Receive(MessageA message) { _ = Interlocked.Increment(ref this.a); } public void Receive(MessageB message) { _ = Interlocked.Increment(ref this.b); } } public sealed class MessageA { public string? Text { get; set; } } public sealed class MessageB { } public partial class GenericRecipient : IRecipient.Message>, IRecipient>, IRecipient { public Message? ReceivedMessage1 { get; private set; } public ValueChangedMessage? ReceivedMessage2 { get; private set; } public MessageA? ReceivedMessage3 { get; private set; } public void Receive(Message message) { ReceivedMessage1 = message; } public void Receive(ValueChangedMessage message) { ReceivedMessage2 = message; } public void Receive(MessageA message) { ReceivedMessage3 = message; } public sealed class Message : ValueChangedMessage> { public Message(List list) : base(list) { } } } }