Managed Code Considerations for TestStand and .NET 8

TestStand 2025 Q2 and later use .NET 8 to run .NET-based tools, utilities, and interfaces. Users working with .NET code in their TestStand projects and sequences should keep the following recommendations and resources in mind when developing their code.

  • You should use the TestStandObjectReleaser utility class in the NationalInstruments.TestStand.Utility DLL under the NationalInstruments.TestStand.Utility namespace to handle TestStand objects in managed code. This class identifies all TestStand objects that can be reached from the root object during construction and ensures proper disposal of these objects in the Dispose method. You should call this utility to handle disposal of TestStand objects in the Dispose method.
    For protected override void Dispose(bool disposing) 
    { 
        using (new TestStandObjectReleaserExcludingEngineAndUIControls(this)) 
        { 
            if (disposing) 
            { 
                components?.Dispose(); 
            } 
            base.Dispose(disposing); 
        } 
    } 
  • You should use the Microsoft.NetCore.App, Microsoft.WindowsDesktop.App, and Microsoft.AspNetCore.App .NET runtimes when developing .NET 8-based TestStand applications and user interfaces. You can use the below format to include dependencies on these .NET runtimes.
    <ItemGroup>
    <FrameworkReference 
    	Include="Microsoft.WindowsDesktop.App" /> 
    <FrameworkReference 
    	Include="Microsoft.AspNetCore.App" />
    </ItemGroup> 
  • The .NET 8 Assembly.Load and Assembly.LoadFrom methods both create new AssemblyLoadContext (ALC) objects with the isCollectible property set to False when called, and load the specified assembly in the newly created ALC object. This can cause typecasting and type missing exceptions when calling classes and methods from an assembly, as the same type may be loaded in multiple ALCs. This is especially likely when attempting to cast to a type with an exact, fully qualified type name results in a Type Mismatch or Type Missing exception. You can avoid this issue by using AssemblyLoadContext.LoadFromAssemblyPath to reference the current ALC instead of Assembly.Load or Assembly.LoadFrom. The LoadFromAssemblyPath method loads the assembly directly into the ALC instance you invoke the method on, preventing you from creating copies of the same assembly in multiple load contexts.
    • If you see this error in a code module, you should load your assembly into the TestStand execution ALC. You can accomplish this by getting a reference to the execution ALC using
      AssemblyLoadContext currentContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly());
      and then loading your assembly dynamically using currentContext.LoadFromAssemblyPath().
    • If you see this error when using .NET Framework modules to load your assembly into the current ALC, use reflection to find the System.Runtime.Loader.AssemblyLoadContext like in the below example.
      Assembly loadedAssembly = null; 
      try 
      { 
        Assembly assemblyLoadContext = Type.GetType("System.Runtime.Loader.AssemblyLoadContext", throwOnError: false); 
        if (assemblyLoadContext != null) 
        { 
            MethodInfo getLoadContext = assemblyLoadContext.GetMethod( 
                "GetLoadContext", 
                BindingFlags.Static | BindingFlags.Public, 
                binder: null, 
                new[] { typeof(Assembly) }, 
                modifiers: null); 
            if (getLoadContext != null) 
            { 
                Assembly thisAssembly = Assembly.GetExecutingAssembly(); 
                object loadContext = getLoadContext.Invoke(null, new[] { thisAssembly }); 
                if (loadContext != null) 
                { 
                    MethodInfo loadFromAssemblyPath = loadContext.GetType().GetMethod( 
                        "LoadFromAssemblyPath", 
                        new Type[] { typeof(string) }); 
                    if (loadFromAssemblyPath != null) 
                    { 
                        loadedAssembly = (Assembly)loadFromAssemblyPath.Invoke(loadContext, new[] { fullPath }); 
                    } 
                } 
            } 
        } 
      } 
      catch 
      { 
        // Ignore any exceptions and just fall back to loading the assembly the .NET Framework-compatible way 
      } 
      loadedAssembly = loadedAssembly ?? Assembly.LoadFrom(fullPath); 
      return loadedAssembly;