System.AppContext class

This article provides supplementary remarks to the reference documentation for this API.

The AppContext class enables library writers to provide a uniform opt-out mechanism for new functionality for their users. It establishes a loosely coupled contract between components in order to communicate an opt-out request. This capability is typically important when a change is made to existing functionality. Conversely, there is already an implicit opt-in for new functionality.

AppContext for library developers

Libraries use the AppContext class to define and expose compatibility switches, while library users can set those switches to affect the library behavior. By default, libraries provide the new functionality, and they only alter it (that is, they provide the previous functionality) if the switch is set. This allows libraries to provide new behavior for an existing API while continuing to support callers who depend on the previous behavior.

Define the switch name

The most common way to allow consumers of your library to opt out of a change of behavior is to define a named switch. Its value element is a name/value pair that consists of the name of a switch and its Boolean value. By default, the switch is always implicitly false, which provides the new behavior (and makes the new behavior opt-in by default). Setting the switch to true enables it, which provides the legacy behavior. Explicitly setting the switch to false also provides the new behavior.

It's beneficial to use a consistent format for switch names, since they're a formal contract exposed by a library. The following are two obvious formats:

  • Switch.namespace.switchname
  • Switch.library.switchname

Once you define and document the switch, callers can use it by calling the AppContext.SetSwitch(String, Boolean) method programmatically. .NET Framework apps can also use the switch by adding an <AppContextSwitchOverrides> element to their application configuration file or by using the registry. For more information about how callers use and set the value of AppContext configuration switches, see the AppContext for library consumers section.

In .NET Framework, when the common language runtime runs an application, it automatically reads the registry's compatibility settings and loads the application configuration file to populate the application's AppContext instance. Because the AppContext instance is populated either programmatically by the caller or by the runtime, .NET Framework apps don't have to take any action, such as calling the SetSwitch method, to configure the AppContext instance.

Check the setting

You can check if a consumer has declared the value of the switch and act appropriately by calling the AppContext.TryGetSwitch method. The method returns true if the switchName argument is found, and its isEnabled argument indicates the value of the switch. Otherwise, the method returns false.

Example

The following example illustrates the use of the AppContext class to allow the customer to choose the original behavior of a library method. The following is version 1.0 of a library named StringLibrary. It defines a SubstringStartsAt method that performs an ordinal comparison to determine the starting index of a substring within a larger string.

using System;
using System.Reflection;

[assembly: AssemblyVersion("1.0.0.0")]

public static class StringLibrary1
{
    public static int SubstringStartsAt(string fullString, string substr)
    {
        return fullString.IndexOf(substr, StringComparison.Ordinal);
    }
}
open System
open System.Reflection

[<assembly: AssemblyVersion("1.0.0.0")>]
do ()

module StringLibrary =
    let substringStartsAt (fullString: string) (substr: string) =
        fullString.IndexOf(substr, StringComparison.Ordinal)
Imports System.Reflection

<Assembly: AssemblyVersion("1.0.0.0")>

Public Class StringLibrary
   Public Shared Function SubstringStartsAt(fullString As String, substr As String) As Integer
      Return fullString.IndexOf(substr, StringComparison.Ordinal)
   End Function
End Class

The following example then uses the library to find the starting index of the substring "archæ" in "The archaeologist". Because the method performs an ordinal comparison, the substring cannot be found.

using System;

public class Example1
{
    public static void Main()
    {
        string value = "The archaeologist";
        string substring = "archæ";
        int position = StringLibrary1.SubstringStartsAt(value, substring);
        if (position >= 0)
            Console.WriteLine("'{0}' found in '{1}' starting at position {2}",
                           substring, value, position);
        else
            Console.WriteLine("'{0}' not found in '{1}'", substring, value);
    }
}
// The example displays the following output:
//       'archæ' not found in 'The archaeologist'
let value = "The archaeologist"
let substring = "archæ"

let position =
    StringLibrary.substringStartsAt value substring

if position >= 0 then
    printfn $"'{substring}' found in '{value}' starting at position {position}"
else
    printfn $"'{substring}' not found in '{value}'"

// The example displays the following output:
//       'archæ' not found in 'The archaeologist'
Public Module Example4
    Public Sub Main()
        Dim value As String = "The archaeologist"
        Dim substring As String = "archæ"
        Dim position As Integer = StringLibrary.SubstringStartsAt(value, substring)
        If position >= 0 Then
            Console.WriteLine("'{0}' found in '{1}' starting at position {2}",
                        substring, value, position)
        Else
            Console.WriteLine("'{0}' not found in '{1}'", substring, value)
        End If
    End Sub
End Module
' The example displays the following output:
'       'archæ' not found in 'The archaeologist'

Version 2.0 of the library, however, changes the SubstringStartsAt method to use culture-sensitive comparison.

using System;
using System.Reflection;

[assembly: AssemblyVersion("2.0.0.0")]

public static class StringLibrary2
{
    public static int SubstringStartsAt(string fullString, string substr)
    {
        return fullString.IndexOf(substr, StringComparison.CurrentCulture);
    }
}
open System
open System.Reflection

[<assembly: AssemblyVersion("2.0.0.0")>]
do ()

module StringLibrary =
    let substringStartsAt (fullString: string) (substr: string) =
        fullString.IndexOf(substr, StringComparison.CurrentCulture)
Imports System.Reflection

<Assembly: AssemblyVersion("2.0.0.0")>

Public Class StringLibrary
   Public Shared Function SubstringStartsAt(fullString As String, substr As String) As Integer
      Return fullString.IndexOf(substr, StringComparison.CurrentCulture)
   End Function
End Class

When the app is recompiled to run against the new version of the library, it now reports that the substring "archæ" is found at index 4 in "The archaeologist".

using System;

public class Example2
{
    public static void Main()
    {
        string value = "The archaeologist";
        string substring = "archæ";
        int position = StringLibrary2.SubstringStartsAt(value, substring);
        if (position >= 0)
            Console.WriteLine("'{0}' found in '{1}' starting at position {2}",
                           substring, value, position);
        else
            Console.WriteLine("'{0}' not found in '{1}'", substring, value);
    }
}
// The example displays the following output:
//       'archæ' found in 'The archaeologist' starting at position 4
let value = "The archaeologist"
let substring = "archæ"

let position =
    StringLibrary.substringStartsAt value substring

if position >= 0 then
    printfn $"'{substring}' found in '{value}' starting at position {position}"
else
    printfn $"'{substring}' not found in '{value}'"

// The example displays the following output:
//       'archæ' found in 'The archaeologist' starting at position 4
Public Module Example6
    Public Sub Main()
        Dim value As String = "The archaeologist"
        Dim substring As String = "archæ"
        Dim position As Integer = StringLibrary.SubstringStartsAt(value, substring)
        If position >= 0 Then
            Console.WriteLine("'{0}' found in '{1}' starting at position {2}",
                        substring, value, position)
        Else
            Console.WriteLine("'{0}' not found in '{1}'", substring, value)
        End If
    End Sub
End Module
' The example displays the following output:
'       'archæ' found in 'The archaeologist' starting at position 4

This change can be prevented from breaking the applications that depend on the original behavior by defining a switch. In this case, the switch is named StringLibrary.DoNotUseCultureSensitiveComparison. Its default value, false, indicates that the library should perform its version 2.0 culture-sensitive comparison. true indicates that the library should perform its version 1.0 ordinal comparison. A slight modification of the previous code allows the library consumer to set the switch to determine the kind of comparison the method performs.

using System;
using System.Reflection;

[assembly: AssemblyVersion("2.0.0.0")]

public static class StringLibrary
{
   public static int SubstringStartsAt(string fullString, string substr)
   {
      bool flag;
      if (AppContext.TryGetSwitch("StringLibrary.DoNotUseCultureSensitiveComparison", out flag) && flag == true)
         return fullString.IndexOf(substr, StringComparison.Ordinal);
      else
         return fullString.IndexOf(substr, StringComparison.CurrentCulture);
   }
}
open System
open System.Reflection

[<assembly: AssemblyVersion("2.0.0.0")>]
do ()

AppContext.SetSwitch("StringLibrary.DoNotUseCultureSensitiveComparison",true)

module StringLibrary =
    let substringStartsAt (fullString: string) (substr: string) =
        match AppContext.TryGetSwitch "StringLibrary.DoNotUseCultureSensitiveComparison" with 
        | true, true -> fullString.IndexOf(substr, StringComparison.Ordinal)
        | _ -> fullString.IndexOf(substr, StringComparison.CurrentCulture)
Imports System.Reflection

<Assembly: AssemblyVersion("2.0.0.0")>

Public Class StringLibrary
   Public Shared Function SubstringStartsAt(fullString As String, substr As String) As Integer
      Dim flag As Boolean
      If AppContext.TryGetSwitch("StringLibrary.DoNotUseCultureSensitiveComparison", flag) AndAlso flag = True Then
         Return fullString.IndexOf(substr, StringComparison.Ordinal)
      Else
         Return fullString.IndexOf(substr, StringComparison.CurrentCulture)
      End If   
   End Function
End Class

A .NET Framework application can then use the following configuration file to restore the version 1.0 behavior.

<configuration>
   <runtime>
      <AppContextSwitchOverrides value="StringLibrary.DoNotUseCultureSensitiveComparison=true" />
   </runtime>
</configuration>

When the application is run with the configuration file present, it produces the following output:

'archæ' not found in 'The archaeologist'

AppContext for library consumers

If you're the consumer of a library, the AppContext class allows you to take advantage of a library or library method's opt-out mechanism for new functionality. Individual methods of the class library that you are calling define particular switches that enable or disable a new behavior. The value of the switch is a Boolean. If it is false, which is typically the default value, the new behavior is enabled; if it is true, the new behavior is disabled, and the member behaves as it did previously.

You can set the value of a switch by calling the AppContext.SetSwitch(String, Boolean) method in your code. The switchName argument defines the switch name, and the isEnabled property defines the value of the switch. Because AppContext is a static class, it is available on a per-application domain basis. Calling the AppContext.SetSwitch(String, Boolean) has application scope; that is, it affects only the application.

.NET Framework apps have additional ways to set the value of a switch:

  • By adding an <AppContextSwitchOverrides> element to the <runtime> section of the app.config file. The switch has a single attribute, value, whose value is a string that represents a key/value pair containing both the switch name and its value.

    To define multiple switches, separate each switch's key/value pair in the <AppContextSwitchOverrides> element's value attribute with a semicolon. In that case, the <AppContextSwitchOverrides> element has the following format:

    <AppContextSwitchOverrides value="switchName1=value1;switchName2=value2" />
    

    Using the <AppContextSwitchOverrides> element to define a configuration setting has application scope; that is, it affects only the application.

    Note

    For information on the switches defined by .NET Framework, see <AppContextSwitchOverrides> element.

  • By adding an entry to the registry. Add a new string value to the HKLM\SOFTWARE\Microsoft\.NETFramework\AppContext subkey. Set the name of the entry to the name of the switch. Set its value to one of the following options: True, true, False, or false. If the runtime encounters any other value, it ignores the switch.

    On a 64-bit operating system, you must also add the same entry to the HKLM\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\AppContext subkey.

    Using the registry to define an AppContext switch has machine scope; that is, it affects every application running on the machine.

For ASP.NET and ASP.NET Core applications, you set a switch by adding an <Add> element to the <appSettings> section of the web.config file. For example:

<appSettings>
   <add key="AppContext.SetSwitch:switchName1" value="switchValue1" />
   <add key="AppContext.SetSwitch:switchName2" value="switchValue2" />
</appSettings>

If you set the same switch in more than one way, the order of precedence for determining which setting overrides the others is:

  1. The programmatic setting.
  2. The setting in the app.config file (for .NET Framework apps) or the web.config file (for ASP.NET Core apps).
  3. The registry setting (for .NET Framework apps only).

See also