diff --git a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ControlHostBehaviourTests.cs b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ControlHostBehaviourTests.cs
new file mode 100644
index 00000000..9be23831
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ControlHostBehaviourTests.cs
@@ -0,0 +1,315 @@
+// Copyright (c) 2019-2026 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Collections;
+using System.Reflection;
+using System.Runtime.Loader;
+using ReactiveUI.SourceGenerators.WinForms;
+
+namespace ReactiveUI.SourceGenerator.Tests;
+
+///
+/// Runs the generated Windows Forms hosts against the desktop stubs: they are constructed, show their default content,
+/// and swap in the view for a routed or hosted view model, without a WhenAny call the binding engine has to
+/// dispatch.
+///
+public class ControlHostBehaviourTests
+{
+ /// The hosts, and a view model, view and view locator to route between.
+ private const string Source = """
+ using System.ComponentModel;
+ using System.Windows.Forms;
+ using ReactiveUI;
+ using ReactiveUI.SourceGenerators.WinForms;
+
+ namespace Hosts
+ {
+ [RoutedControlHost("System.Windows.Forms.UserControl")]
+ public partial class RoutedHost
+ {
+ private IContainer? components;
+
+ private void InitializeComponent()
+ {
+ }
+ }
+
+ [ViewModelControlHost("System.Windows.Forms.UserControl")]
+ public partial class ViewModelHost
+ {
+ private IContainer? components;
+
+ private void InitializeComponent()
+ {
+ }
+ }
+
+ public sealed class Shell : IScreen
+ {
+ public RoutingState Router { get; } = new();
+ }
+
+ public sealed class PageViewModel(IScreen screen) : ReactiveObject, IRoutableViewModel
+ {
+ public string UrlPathSegment => "page";
+
+ public IScreen HostScreen => screen;
+ }
+
+ public sealed class PageView : UserControl, IViewFor
+ {
+ public PageViewModel? ViewModel { get; set; }
+
+ object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = (PageViewModel?)value; }
+ }
+
+ public sealed class Locator : IViewLocator
+ {
+ public IViewFor? ResolveView() where TViewModel : class => null;
+
+ public IViewFor? ResolveView(string? contract) where TViewModel : class => null;
+
+ public IViewFor? ResolveView(object? viewModel) => viewModel is PageViewModel ? new PageView() : null;
+
+ public IViewFor? ResolveView(object? viewModel, string? contract) => ResolveView(viewModel);
+ }
+ }
+ """;
+
+ /// The hosts' default content property.
+ private const string DefaultContent = nameof(DefaultContent);
+
+ /// The routed host's router property.
+ private const string Router = nameof(Router);
+
+ /// The view-model host's view model property, and a view's.
+ private const string ViewModel = nameof(ViewModel);
+
+ /// The assembly compiled from , built once.
+ private static readonly Lazy HostAssembly = new(Compile, LazyThreadSafetyMode.ExecutionAndPublication);
+
+ /// The routed host shows its default content, then the view for the view model the router navigates to.
+ /// A task to monitor the async.
+ [Test]
+ public async Task RoutedHostShowsDefaultContentThenTheRoutedView()
+ {
+ var host = Create("Hosts.RoutedHost");
+ var defaultContent = Create("System.Windows.Forms.UserControl");
+ Set(host, nameof(ViewLocator), Create("Hosts.Locator"));
+
+ Set(host, DefaultContent, defaultContent);
+ await AssertShowsOnly(host, defaultContent);
+
+ var shell = Create("Hosts.Shell");
+ var router = Get(shell, Router);
+ Set(host, Router, router);
+ var navigationStack = (IList)Get(router, "NavigationStack");
+ _ = navigationStack.Add(Activator.CreateInstance(HostAssembly.Value.GetType("Hosts.PageViewModel", throwOnError: true)!, shell));
+
+ var shown = ShownControls(host);
+ await Assert.That(shown.Count).IsEqualTo(1);
+ await Assert.That(shown[0]!.GetType().FullName).IsEqualTo("Hosts.PageView");
+ }
+
+ /// The view-model host shows its default content, then the view for the view model it is given.
+ /// A task to monitor the async.
+ [Test]
+ public async Task ViewModelHostShowsDefaultContentThenTheViewForItsViewModel()
+ {
+ var host = Create("Hosts.ViewModelHost");
+ var defaultContent = Create("System.Windows.Forms.UserControl");
+ Set(host, nameof(ViewLocator), Create("Hosts.Locator"));
+
+ Set(host, DefaultContent, defaultContent);
+ await AssertShowsOnly(host, defaultContent);
+
+ var viewModel = Activator.CreateInstance(HostAssembly.Value.GetType("Hosts.PageViewModel", throwOnError: true)!, Create("Hosts.Shell"))!;
+ Set(host, ViewModel, viewModel);
+
+ var shown = ShownControls(host);
+ await Assert.That(shown.Count).IsEqualTo(1);
+ await Assert.That(shown[0]!.GetType().FullName).IsEqualTo("Hosts.PageView");
+ await Assert.That(Get(shown[0]!, ViewModel)).IsSameReferenceAs(viewModel);
+ }
+
+ /// A host in the global namespace, and a host nested in another type, get their generated members.
+ /// A task to monitor the async.
+ [Test]
+ public async Task HostsInTheGlobalNamespaceAndNestedTypesGetTheirMembers()
+ {
+ const string source = """
+ using System.ComponentModel;
+ using ReactiveUI.SourceGenerators.WinForms;
+
+ [RoutedControlHost("System.Windows.Forms.UserControl")]
+ public partial class GlobalRoutedHost
+ {
+ private IContainer? components;
+
+ private void InitializeComponent()
+ {
+ }
+ }
+
+ namespace Hosts
+ {
+ public partial class Outer
+ {
+ [ViewModelControlHost("System.Windows.Forms.UserControl")]
+ public partial class NestedViewModelHost
+ {
+ private IContainer? components;
+
+ private void InitializeComponent()
+ {
+ }
+ }
+ }
+ }
+ """;
+ var (output, _) = Generate(source);
+
+ await Assert.That(output.GetTypeByMetadataName("GlobalRoutedHost")!.GetMembers(Router)).IsNotEmpty();
+ await Assert.That(output.GetTypeByMetadataName("Hosts.Outer+NestedViewModelHost")!.GetMembers(ViewModel)).IsNotEmpty();
+ await Assert.That(output.GetTypeByMetadataName("NestedViewModelHost")).IsNull();
+ await Assert.That(GetErrors(output)).IsEmpty();
+ }
+
+ /// Neither host calls WhenAny, which the binding engine could not dispatch from generated code.
+ /// A task to monitor the async.
+ [Test]
+ public async Task HostsDoNotCallWhenAny()
+ {
+ foreach (var generated in Generate(Source).GeneratedSources)
+ {
+ await Assert.That(generated).DoesNotContain("WhenAny");
+ }
+ }
+
+ /// Creates an instance of a type from the compiled assembly or its references.
+ /// The type's full name.
+ /// The instance.
+ private static object Create(string typeName) => Activator.CreateInstance(HostAssembly.Value.GetType(typeName, throwOnError: false) ?? FindType(typeName))!;
+
+ /// Finds a type in the compiled assembly's load context.
+ /// The type's full name.
+ /// The type.
+ private static Type FindType(string typeName)
+ {
+ foreach (var assembly in AssemblyLoadContext.GetLoadContext(HostAssembly.Value)!.Assemblies)
+ {
+ if (assembly.GetType(typeName, throwOnError: false) is { } type)
+ {
+ return type;
+ }
+ }
+
+ throw new InvalidOperationException($"{typeName} was not found.");
+ }
+
+ /// Reads a public property.
+ /// The object.
+ /// The property name.
+ /// The property's value.
+ private static object Get(object target, string name) => target.GetType().GetProperty(name)!.GetValue(target)!;
+
+ /// Sets a public property.
+ /// The object.
+ /// The property name.
+ /// The value.
+ private static void Set(object target, string name, object? value) => target.GetType().GetProperty(name)!.SetValue(target, value);
+
+ /// Lists the controls a host shows.
+ /// The host.
+ /// The controls.
+ private static List