diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index ecefa338c9..c18aea1e32 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -65,22 +65,6 @@ internal static partial class ADP
///
internal const int MaxBufferAccessTokenExpiry = 600;
- ///
- /// This member returns true if the current OS platform is Windows.
- ///
- ///
- /// This is a const on .NET Framework, and a property on .NET Core, because of differing API availability and JIT requirements.
- /// .NET Framework will perform basic dead branch elimination when a const value is encountered, while .NET Core can trim Windows-specific
- /// code when published to non-Windows platforms.
- /// .NET Core's trimming is very limited though, so this must be used inline within methods to throw PlatformNotSupportedException,
- /// rather than in a throw helper.
- ///
- #if NETFRAMEWORK
- public const bool IsWindows = true;
- #else
- public static bool IsWindows => OperatingSystem.IsWindows();
- #endif
-
#region UDT
#if NETFRAMEWORK
@@ -159,10 +143,10 @@ internal static Timer UnsafeCreateTimer(
state,
TimeSpan.FromMilliseconds(dueTimeMilliseconds),
TimeSpan.FromMilliseconds(periodMilliseconds));
-
+
internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
- // Don't capture the current ExecutionContext and its AsyncLocals onto
+ // Don't capture the current ExecutionContext and its AsyncLocals onto
// a global timer causing them to live forever
bool restoreFlow = false;
try
@@ -184,7 +168,7 @@ internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, Ti
}
}
}
-
+
#region COM+ exceptions
internal static ArgumentException Argument(string error)
@@ -451,7 +435,7 @@ internal static ArgumentOutOfRangeException InvalidCommandBehavior(CommandBehavi
internal static object LocalMachineRegistryValue(string subkey, string queryvalue)
{
#if NET
- if (!IsWindows)
+ if (!OsConstants.IsWindows)
{
// No registry in non-Windows environments
return null;
@@ -639,7 +623,7 @@ internal static string BuildMultiPartName(string[] strings)
{
StringBuilder bld = new();
// Assume we want to build a full multi-part name with all parts except trimming separators for
- // leading empty names (null or empty strings, but not whitespace). Separators in the middle
+ // leading empty names (null or empty strings, but not whitespace). Separators in the middle
// should be added, even if the name part is null/empty, to maintain proper location of the parts.
for (int i = 0; i < strings.Length; i++)
{
@@ -839,14 +823,14 @@ internal static Version GetAssemblyVersion()
/// Represents a collection of Azure SQL Server endpoint URLs for various regions and environments.
///
/// This array includes endpoint URLs for Azure SQL in global, Germany, US Government,
- /// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services
+ /// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services
/// in their respective regions or environments.
internal static readonly List s_azureSqlServerEndpoints = new() { AZURE_SQL,
AZURE_SQL_GERMANY,
AZURE_SQL_USGOV,
AZURE_SQL_CHINA,
AZURE_SQL_FABRIC };
-
+
///
/// Contains endpoint strings for Azure SQL Server on-demand services.
/// Each entry is a combination of the ONDEMAND_PREFIX and a specific Azure SQL endpoint string.
@@ -872,9 +856,9 @@ internal static Version GetAssemblyVersion()
internal static bool IsAzureSynapseOnDemandEndpoint(string dataSource)
{
return IsEndpoint(dataSource, s_azureSynapseOnDemandEndpoints)
- || dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0;
+ || dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0;
}
-
+
internal static bool IsAzureSqlServerEndpoint(string dataSource)
{
return IsEndpoint(dataSource, s_azureSqlServerEndpoints);
@@ -1088,7 +1072,7 @@ internal enum InternalErrorCode
SqlDependencyObtainProcessDispatcherFailureObjectHandle = 50,
SqlDependencyProcessDispatcherFailureCreateInstance = 51,
-
+
SqlDependencyCommandHashIsNotAssociatedWithNotification = 53,
UnknownTransactionFailure = 60,
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
index 16e0abb7ec..c93eeb2359 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
@@ -562,7 +562,7 @@ public static bool UseManagedNetworking
return s_useManagedNetworking == SwitchValue.True;
}
- if (!OperatingSystem.IsWindows())
+ if (!OsConstants.IsWindows)
{
s_useManagedNetworking = SwitchValue.True;
return true;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
new file mode 100644
index 0000000000..09f2c27960
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
@@ -0,0 +1,70 @@
+// 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.Runtime.InteropServices;
+
+namespace Microsoft.Data.SqlClient;
+
+///
+/// Provides platform detection flags for OS-specific code paths.
+///
+///
+/// These constants are computed at runtime and cached as static readonly fields. This design
+/// allows the JIT compiler to elide branches in hot paths based on whether the OS flags are known
+/// constants at JIT compilation time.
+///
+internal static class OsConstants
+{
+ ///
+ /// Gets a value indicating whether the runtime is executing on Windows.
+ ///
+ internal static readonly bool IsWindows;
+
+ ///
+ /// Gets a value indicating whether the runtime is executing on Linux.
+ ///
+ internal static readonly bool IsLinux;
+
+ ///
+ /// Gets a value indicating whether the runtime is executing on macOS.
+ ///
+ internal static readonly bool IsMacOS;
+
+ #if NET
+ ///
+ /// Gets a value indicating whether the runtime is executing on FreeBSD.
+ ///
+ ///
+ /// FreeBSD support is only available in .NET 5+ and later. This field will be
+ /// false on .NET Framework or if the runtime does not support FreeBSD detection.
+ ///
+ internal static readonly bool IsFreeBSD;
+ #endif
+
+ ///
+ /// Initializes platform detection flags by querying .
+ ///
+ ///
+ /// We use a static constructor instead of a module initializer ([ModuleInitializer]) to avoid
+ /// the CA2255 security concern. Module initializers can be problematic because: 1. They run in
+ /// an unpredictable order relative to other initialization code. 2. They run before the app
+ /// initialization sequence, potentially before security policies are set. 3. They can
+ /// complicate debugging and profiling.
+ ///
+ /// Using a static constructor ensures initialization happens in a well-defined, type-safe
+ /// manner that is compatible with the CLR's type loading guarantees.
+ ///
+ /// The trade-off is that the OS flags won't be initialized until the OsConstants type is first
+ /// accessed, which may cause a slight delay in a hot path, but only once.
+ ///
+ static OsConstants()
+ {
+ IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ #if NET
+ IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
+ #endif
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs
index b02dd24af1..f0438cd162 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs
@@ -57,7 +57,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe
/// Gets a string array containing valid certificate locations.
///
private static string[] ValidCertificateLocations =>
- Environment.OSVersion.Platform == PlatformID.Win32NT
+ OsConstants.IsWindows
? [CertLocationLocalMachine, CertLocationCurrentUser]
: [CertLocationCurrentUser];
@@ -158,14 +158,13 @@ private static RSA GetCertificatePrivateKeyByPath(string keyPath, bool isSystemO
}
// Extract the store location where the cert is stored
- if (storeLocationSpan.IsEmpty
- && Environment.OSVersion.Platform == PlatformID.Win32NT)
+ if (storeLocationSpan.IsEmpty && OsConstants.IsWindows)
{
// Default to Local Machine on Windows. Non-Windows platforms only support CurrentUser
storeLocation = StoreLocation.LocalMachine;
}
else if (storeLocationSpan.Equals(CertLocationLocalMachine.AsSpan(), StringComparison.OrdinalIgnoreCase)
- && Environment.OSVersion.Platform == PlatformID.Win32NT)
+ && OsConstants.IsWindows)
{
storeLocation = StoreLocation.LocalMachine;
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs
index fc65808cf1..4d0d79b750 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs
@@ -149,7 +149,7 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out
///
public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
- if (!ADP.IsWindows)
+ if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
@@ -180,7 +180,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string?
///
public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
- if (!ADP.IsWindows)
+ if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
@@ -211,7 +211,7 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string?
///
public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
- throw ADP.IsWindows
+ throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
@@ -219,7 +219,7 @@ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool a
///
public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
- throw ADP.IsWindows
+ throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs
index 73ee585dfa..dc956d9c71 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs
@@ -151,7 +151,7 @@ private static int GetProviderType(string providerName, string keyPath, bool isS
///
public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
- if (!ADP.IsWindows)
+ if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
@@ -182,7 +182,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string?
///
public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
- if (!ADP.IsWindows)
+ if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
@@ -213,7 +213,7 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string?
///
public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
- throw ADP.IsWindows
+ throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
@@ -221,7 +221,7 @@ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool a
///
public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
- throw ADP.IsWindows
+ throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 96312a491d..52522c7266 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -1376,7 +1376,7 @@ internal static Exception LargeCertificatePathLength(int actualLength, int maxLe
internal static Exception NullCertificatePath(string[] validLocations, bool isSystemOp)
{
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
@@ -1428,7 +1428,7 @@ internal static Exception NullCngKeyPath(bool isSystemOp)
internal static Exception InvalidCertificatePath(string actualCertificatePath, string[] validLocations, bool isSystemOp)
{
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
@@ -1564,7 +1564,7 @@ internal static Exception InvalidCngKey(string masterKeyPath, string cngProvider
internal static Exception InvalidCertificateLocation(string certificateLocation, string certificatePath, string[] validLocations, bool isSystemOp)
{
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
index 29352c5460..920f96d07d 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -981,7 +981,7 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
#if NET
- if (OperatingSystem.IsWindows())
+ if (OsConstants.IsWindows)
#endif
{
error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocol);
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
index 56ead6a4a2..ce24fd88c0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
@@ -4,7 +4,6 @@
using System;
using System.Globalization;
-using System.Runtime.InteropServices;
using Microsoft.Data.Common;
namespace Microsoft.Data.SqlClient
@@ -173,7 +172,7 @@ static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly()
byte[] nicAddress = null;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (OsConstants.IsWindows)
{
// NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key
// has a value that is not zero, then we cannot use the NetworkAddress key and must
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs
index a8fcf7ca88..63cd220d6c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs
@@ -120,25 +120,24 @@ static UserAgent()
// specific values.
//
string osType = Unknown;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (OsConstants.IsWindows)
{
osType = Windows;
}
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ else if (OsConstants.IsLinux)
{
osType = Linux;
}
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ else if (OsConstants.IsMacOS)
{
osType = macOS;
}
- // The FreeBSD platform doesn't exist in .NET Framework at all.
- #if NET
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
+#if NET
+ else if (OsConstants.IsFreeBSD)
{
osType = FreeBSD;
}
- #endif
+#endif
// Build it!
Value = Build(
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs
index 4c48890fc4..6059705910 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs
@@ -102,7 +102,7 @@ public SqlFileStream(
FileOptions options,
long allocationSize)
{
- if (!ADP.IsWindows)
+ if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported);
}