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 568592072d..ad58f4072e 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
@@ -441,7 +425,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;
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 d951bb5d30..85f8c87937 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs
@@ -635,7 +635,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..b496141693
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
@@ -0,0 +1,107 @@
+// 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;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Data.SqlClient;
+
+///
+/// Provides platform detection flags for OS-specific code paths.
+///
+///
+/// This type exists to keep OS detection terse and in one place: callers write
+/// OsConstants.IsWindows instead of repeating RuntimeInformation.IsOSPlatform(...)
+/// throughout the codebase. Centralizing it also gives us a single seam to adjust if a specific
+/// OS needs special handling (for example, a different detection mechanism, a finer-grained
+/// flag, or an override for testing) without touching every call site.
+///
+///
+/// Each flag is exposed as a property that directly returns an OS check. On .NET, that check is
+/// OperatingSystem.Is*(), which the JIT treats as an intrinsic and constant-folds (and which
+/// the IL trimmer also recognizes). On .NET Framework — where the
+/// platform-check helpers do not exist — the check falls back to
+/// . Either way, returning the check directly is
+/// deliberate: caching the results in static fields (for example, via a static constructor) would
+/// defeat both the JIT folding and the trimmer — the trimmer cannot analyze a static constructor,
+/// so it would be unable to prove which branches are dead and would keep OS-specific code (including
+/// Windows-only native dependencies) in apps published for other platforms.
+///
+///
+///
+/// IL trimming note: when a downstream app is published for a specific OS (a RID-specific publish),
+/// the IL trimmer substitutes the underlying /
+/// OperatingSystem.Is* checks with constant true/false for the target OS and
+/// then removes the dead branch (along with everything it transitively references, such as
+/// Windows-only native entry points). This is what lets a single cross-platform assembly trim
+/// away the OS-specific code that does not apply to the published target.
+///
+///
+/// For this to work, each guard must be used inline in the same method as the OS-specific
+/// code it gates. The trimmer's constant folding is shallow: it reasons within the method that
+/// contains the guard, so a guard hidden behind a helper traps the constant inside that helper and
+/// leaves the protected code reachable (and therefore not trimmed).
+///
+///
+/// Do — guard inline, so the trimmer can drop the branch and its dependencies:
+///
+/// if (OsConstants.IsWindows)
+/// {
+/// WindowsOnlyNativeCall(); // becomes `if (false) { ... }` off-Windows, then removed
+/// }
+///
+///
+///
+/// Don't — hide the guard in a throw helper; the trimmer cannot prove the call below is dead:
+///
+/// static void ThrowIfNotWindows()
+/// {
+/// if (!OsConstants.IsWindows) throw new PlatformNotSupportedException();
+/// }
+/// // caller:
+/// ThrowIfNotWindows();
+/// WindowsOnlyNativeCall(); // still reachable to the trimmer; kept even off-Windows
+///
+///
+///
+internal static class OsConstants
+{
+ ///
+ /// Gets a value indicating whether the runtime is executing on Windows.
+ ///
+ #if NET
+ internal static bool IsWindows => OperatingSystem.IsWindows();
+ #else
+ internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ #endif
+
+ ///
+ /// Gets a value indicating whether the runtime is executing on Linux.
+ ///
+ #if NET
+ internal static bool IsLinux => OperatingSystem.IsLinux();
+ #else
+ internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ #endif
+
+ ///
+ /// Gets a value indicating whether the runtime is executing on macOS.
+ ///
+ #if NET
+ internal static bool IsMacOS => OperatingSystem.IsMacOS();
+ #else
+ internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ #endif
+
+ #if NET
+ ///
+ /// Gets a value indicating whether the runtime is executing on FreeBSD.
+ ///
+ ///
+ /// FreeBSD support is only available in .NET 5+ and later. This property will be
+ /// false on .NET Framework or if the runtime does not support FreeBSD detection.
+ ///
+ internal static bool IsFreeBSD => OperatingSystem.IsFreeBSD();
+ #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 678697d221..e73a69fd04 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -1141,7 +1141,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)
@@ -1193,7 +1193,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)
@@ -1329,7 +1329,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 c3f96b077a..660bfdccde 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -979,7 +979,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 a384095254..2dcd38c7d9 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);
}