diff --git a/Terminal.Gui/App/Application.Lifecycle.cs b/Terminal.Gui/App/Application.Lifecycle.cs
index 802ba7a53..2d1465fb0 100644
--- a/Terminal.Gui/App/Application.Lifecycle.cs
+++ b/Terminal.Gui/App/Application.Lifecycle.cs
@@ -18,7 +18,15 @@ public static partial class Application // Lifecycle (Init/Shutdown)
/// instance for all subsequent application operations.
///
/// A new instance.
- public static IApplication Create () { return new ApplicationImpl (); }
+ ///
+ /// Thrown if the legacy static Application model has already been used in this process.
+ ///
+ public static IApplication Create ()
+ {
+ ApplicationImpl.MarkInstanceBasedModelUsed ();
+
+ return new ApplicationImpl ();
+ }
///
[RequiresUnreferencedCode ("AOT")]
@@ -65,5 +73,12 @@ public static partial class Application // Lifecycle (Init/Shutdown)
// guaranteeing that the state of this singleton is deterministic when Init
// starts running and after Shutdown returns.
[Obsolete ("The legacy static Application object is going away.")]
- internal static void ResetState (bool ignoreDisposed = false) => ApplicationImpl.Instance?.ResetState (ignoreDisposed);
+ internal static void ResetState (bool ignoreDisposed = false)
+ {
+ // Reset the model usage tracking first to allow access to Instance if needed
+ ApplicationImpl.ResetModelUsageTracking ();
+
+ // Now safe to access Instance for cleanup
+ ApplicationImpl.Instance?.ResetState (ignoreDisposed);
+ }
}
diff --git a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
index ed1e98741..18db0fa6d 100644
--- a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
+++ b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
@@ -273,6 +273,10 @@ public partial class ApplicationImpl
// gui.cs does no longer process any callbacks. See #1084 for more details:
// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
SynchronizationContext.SetSynchronizationContext (null);
+
+ // === 12. Reset application model usage tracking ===
+ // Reset the model usage tracking to allow the process to use either model after shutdown
+ ResetModelUsageTracking ();
}
///
diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs
index 2dc54cbda..9277a266d 100644
--- a/Terminal.Gui/App/ApplicationImpl.cs
+++ b/Terminal.Gui/App/ApplicationImpl.cs
@@ -21,6 +21,11 @@ public partial class ApplicationImpl : IApplication
#region Singleton
+ ///
+ /// Tracks which application model has been used in this process.
+ ///
+ private static ApplicationModelUsage _modelUsage = ApplicationModelUsage.None;
+
///
/// Configures the singleton instance of to use the specified backend implementation.
///
@@ -33,10 +38,72 @@ public partial class ApplicationImpl : IApplication
///
/// Gets the currently configured backend implementation of gateway methods.
///
- public static IApplication Instance => _instance ??= new ApplicationImpl ();
+ public static IApplication Instance
+ {
+ get
+ {
+ // If an instance already exists, return it without fence checking
+ // This allows for cleanup/reset operations
+ if (_instance is { })
+ {
+ return _instance;
+ }
+
+ // Only check the fence when creating a new instance
+ if (_modelUsage == ApplicationModelUsage.InstanceBased)
+ {
+ throw new InvalidOperationException (
+ "Cannot use legacy static Application model (Application.Init/ApplicationImpl.Instance) after using modern instance-based model (Application.Create). " +
+ "Use only one model per process.");
+ }
+
+ _modelUsage = ApplicationModelUsage.LegacyStatic;
+
+ return _instance = new ApplicationImpl ();
+ }
+ }
+
+ ///
+ /// INTERNAL: Marks that the instance-based model has been used. Called by Application.Create().
+ ///
+ internal static void MarkInstanceBasedModelUsed ()
+ {
+ if (_modelUsage == ApplicationModelUsage.LegacyStatic)
+ {
+ throw new InvalidOperationException (
+ "Cannot use modern instance-based model (Application.Create) after using legacy static Application model (Application.Init/ApplicationImpl.Instance). " +
+ "Use only one model per process.");
+ }
+
+ _modelUsage = ApplicationModelUsage.InstanceBased;
+ }
+
+ ///
+ /// INTERNAL: Resets the model usage tracking. Only for testing purposes.
+ ///
+ internal static void ResetModelUsageTracking ()
+ {
+ _modelUsage = ApplicationModelUsage.None;
+ _instance = null;
+ }
#endregion Singleton
+ ///
+ /// Defines the different application usage models.
+ ///
+ private enum ApplicationModelUsage
+ {
+ /// No model has been used yet.
+ None,
+
+ /// Legacy static model (Application.Init/ApplicationImpl.Instance).
+ LegacyStatic,
+
+ /// Modern instance-based model (Application.Create).
+ InstanceBased
+ }
+
private string? _driverName;
#region Input
diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationModelFencingTests.cs b/Tests/UnitTestsParallelizable/Application/ApplicationModelFencingTests.cs
new file mode 100644
index 000000000..b9728c151
--- /dev/null
+++ b/Tests/UnitTestsParallelizable/Application/ApplicationModelFencingTests.cs
@@ -0,0 +1,152 @@
+namespace UnitTests.ApplicationTests;
+
+///
+/// Tests to ensure that mixing legacy static Application and modern instance-based models
+/// throws appropriate exceptions.
+///
+[Collection ("Global Test Setup")]
+public class ApplicationModelFencingTests
+{
+ public ApplicationModelFencingTests ()
+ {
+ // Reset the model usage tracking before each test
+ ApplicationImpl.ResetModelUsageTracking ();
+ }
+
+ [Fact]
+ public void Create_ThenInstanceAccess_ThrowsInvalidOperationException ()
+ {
+ // Create a modern instance-based application
+ IApplication app = Application.Create ();
+
+ // Attempting to access the legacy static instance should throw
+ InvalidOperationException ex = Assert.Throws (() =>
+ {
+ IApplication _ = ApplicationImpl.Instance;
+ });
+
+ Assert.Contains ("Cannot use legacy static Application model", ex.Message);
+ Assert.Contains ("after using modern instance-based model", ex.Message);
+
+ // Clean up
+ app.Shutdown ();
+ }
+
+ [Fact]
+ public void InstanceAccess_ThenCreate_ThrowsInvalidOperationException ()
+ {
+ // Access the legacy static instance
+ IApplication staticInstance = ApplicationImpl.Instance;
+
+ // Attempting to create a modern instance-based application should throw
+ InvalidOperationException ex = Assert.Throws (() =>
+ {
+ IApplication _ = Application.Create ();
+ });
+
+ Assert.Contains ("Cannot use modern instance-based model", ex.Message);
+ Assert.Contains ("after using legacy static Application model", ex.Message);
+
+ // Clean up
+ staticInstance.Shutdown ();
+ }
+
+ [Fact]
+ public void Init_ThenCreate_ThrowsInvalidOperationException ()
+ {
+ // Initialize using legacy static API
+ IApplication staticInstance = ApplicationImpl.Instance;
+ staticInstance.Init ("fake");
+
+ // Attempting to create a modern instance-based application should throw
+ InvalidOperationException ex = Assert.Throws (() =>
+ {
+ IApplication _ = Application.Create ();
+ });
+
+ Assert.Contains ("Cannot use modern instance-based model", ex.Message);
+ Assert.Contains ("after using legacy static Application model", ex.Message);
+
+ // Clean up
+ staticInstance.Shutdown ();
+ }
+
+ [Fact]
+ public void Create_ThenInit_ThrowsInvalidOperationException ()
+ {
+ // Create a modern instance-based application
+ IApplication app = Application.Create ();
+ app.Init ("fake");
+
+ // Attempting to access the legacy static instance should throw
+ // (Init calls ApplicationImpl.Instance internally)
+ InvalidOperationException ex = Assert.Throws (() =>
+ {
+ IApplication _ = ApplicationImpl.Instance;
+ });
+
+ Assert.Contains ("Cannot use legacy static Application model", ex.Message);
+ Assert.Contains ("after using modern instance-based model", ex.Message);
+
+ // Clean up
+ app.Shutdown ();
+ }
+
+ [Fact]
+ public void MultipleCreate_Calls_DoNotThrow ()
+ {
+ // Multiple calls to Create should not throw
+ IApplication app1 = Application.Create ();
+ IApplication app2 = Application.Create ();
+ IApplication app3 = Application.Create ();
+
+ Assert.NotNull (app1);
+ Assert.NotNull (app2);
+ Assert.NotNull (app3);
+
+ // Clean up
+ app1.Shutdown ();
+ app2.Shutdown ();
+ app3.Shutdown ();
+ }
+
+ [Fact]
+ public void MultipleInstanceAccess_DoesNotThrow ()
+ {
+ // Multiple accesses to Instance should not throw (it's a singleton)
+ IApplication instance1 = ApplicationImpl.Instance;
+ IApplication instance2 = ApplicationImpl.Instance;
+ IApplication instance3 = ApplicationImpl.Instance;
+
+ Assert.NotNull (instance1);
+ Assert.Same (instance1, instance2);
+ Assert.Same (instance2, instance3);
+
+ // Clean up
+ instance1.Shutdown ();
+ }
+
+ [Fact]
+ public void ResetModelUsageTracking_AllowsSwitchingModels ()
+ {
+ // Use modern model
+ IApplication app1 = Application.Create ();
+ app1.Shutdown ();
+
+ // Reset the tracking
+ ApplicationImpl.ResetModelUsageTracking ();
+
+ // Should now be able to use legacy model
+ IApplication staticInstance = ApplicationImpl.Instance;
+ Assert.NotNull (staticInstance);
+ staticInstance.Shutdown ();
+
+ // Reset again
+ ApplicationImpl.ResetModelUsageTracking ();
+
+ // Should be able to use modern model again
+ IApplication app2 = Application.Create ();
+ Assert.NotNull (app2);
+ app2.Shutdown ();
+ }
+}