From 4c08ef5df3862ad8c75e71471d6e6ae8ce6b555c Mon Sep 17 00:00:00 2001
From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com>
Date: Fri, 1 May 2026 12:35:48 -0300
Subject: [PATCH 1/4] Consolidate OS detection to OsConstants
- Create new OsConstants class with IsWindows, IsLinux, IsMacOS, IsFreeBSD, and IsUnix fields
- Add comprehensive XML documentation and explain why static constructor is preferred over [ModuleInitializer] (CA2255 rule)
- Replace all RuntimeInformation.IsOSPlatform() calls with OsConstants fields in:
- TdsParserStaticMethods.cs
- UserAgent.cs
- AdapterUtil.cs (now forwards to OsConstants)
- Replace Environment.OSVersion.Platform checks with OsConstants in:
- SqlColumnEncryptionCertificateStoreProvider.cs (3 instances)
- SqlUtil.cs (3 instances)
- Replace OperatingSystem static methods with OsConstants in:
- LocalAppContextSwitches.cs (uses IsUnix for clearer semantics)
- TdsParser.cs
- Replace all ADP.IsWindows usage with OsConstants in:
- SqlFileStream.cs
- SqlColumnEncryptionCngProvider.cs (4 instances)
- SqlColumnEncryptionCspProvider.cs (4 instances)
- Remove unused AdapterUtil.IsWindows property
Benefits:
- Single point of control for OS detection
- Improved JIT optimization through static readonly cached flags
- Clearer semantics with IsUnix flag for Unix-like platforms
- Consistent API surface across the codebase
---
.../src/Microsoft/Data/Common/AdapterUtil.cs | 20 +----
.../Data/SqlClient/LocalAppContextSwitches.cs | 2 +-
.../Microsoft/Data/SqlClient/OsConstants.cs | 82 +++++++++++++++++++
...olumnEncryptionCertificateStoreProvider.cs | 7 +-
.../SqlColumnEncryptionCngProvider.cs | 8 +-
.../SqlColumnEncryptionCspProvider.cs | 8 +-
.../src/Microsoft/Data/SqlClient/SqlUtil.cs | 6 +-
.../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +-
.../Data/SqlClient/TdsParserStaticMethods.cs | 3 +-
.../src/Microsoft/Data/SqlClient/UserAgent.cs | 13 ++-
.../Microsoft/Data/SqlTypes/SqlFileStream.cs | 2 +-
11 files changed, 108 insertions(+), 45 deletions(-)
create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
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..30c4051cc1 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -13,13 +13,13 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Security;
+using Microsoft.Data.SqlClient;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.Data.Common.ConnectionString;
-using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.Connection;
using Microsoft.SqlServer.Server;
using Microsoft.Win32;
@@ -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.IsUnix)
{
// 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..49fa9f1eba 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.IsUnix)
{
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..6c0a7c7e39
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
@@ -0,0 +1,82 @@
+// 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
+
+ ///
+ /// Gets a value indicating whether the runtime is executing on a Unix-like operating system.
+ ///
+ ///
+ /// This is true for Linux, macOS, FreeBSD, and other Unix-like platforms.
+ /// It is false only on Windows.
+ ///
+ internal static readonly bool IsUnix;
+
+ ///
+ /// 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);
+ IsUnix = IsLinux || IsMacOS || IsFreeBSD;
+#else
+ IsUnix = IsLinux || IsMacOS;
+#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..d585e00cdd 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.IsUnix)
{
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.IsUnix)
{
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..1ba0d47f63 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.IsUnix)
{
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.IsUnix)
{
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..8278d4c23c 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.IsUnix)
{
throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported);
}
From 8f5e2d0893ea3cd07ba2e6547582ef4bb93fb1df Mon Sep 17 00:00:00 2001
From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com>
Date: Fri, 1 May 2026 12:50:42 -0300
Subject: [PATCH 2/4] Address Copilot OS guard feedback
- Remove OsConstants.IsUnix to avoid non-Windows ambiguity
- Update Windows-only guards in SqlFileStream, CNG, CSP, and switch logic
- Keep explicit OS flags (Windows/Linux/macOS/FreeBSD)
---
.../src/Microsoft/Data/Common/AdapterUtil.cs | 4 ++--
.../Data/SqlClient/LocalAppContextSwitches.cs | 2 +-
.../Microsoft/Data/SqlClient/OsConstants.cs | 20 ++++---------------
.../SqlColumnEncryptionCngProvider.cs | 4 ++--
.../SqlColumnEncryptionCspProvider.cs | 4 ++--
.../Microsoft/Data/SqlTypes/SqlFileStream.cs | 2 +-
6 files changed, 12 insertions(+), 24 deletions(-)
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 30c4051cc1..ad58f4072e 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -13,13 +13,13 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Security;
-using Microsoft.Data.SqlClient;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.Data.Common.ConnectionString;
+using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.Connection;
using Microsoft.SqlServer.Server;
using Microsoft.Win32;
@@ -425,7 +425,7 @@ internal static ArgumentOutOfRangeException InvalidCommandBehavior(CommandBehavi
internal static object LocalMachineRegistryValue(string subkey, string queryvalue)
{
#if NET
- if (OsConstants.IsUnix)
+ 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 49fa9f1eba..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 (OsConstants.IsUnix)
+ 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
index 6c0a7c7e39..09f2c27960 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
@@ -31,7 +31,7 @@ internal static class OsConstants
///
internal static readonly bool IsMacOS;
-#if NET
+ #if NET
///
/// Gets a value indicating whether the runtime is executing on FreeBSD.
///
@@ -40,16 +40,7 @@ internal static class OsConstants
/// false on .NET Framework or if the runtime does not support FreeBSD detection.
///
internal static readonly bool IsFreeBSD;
-#endif
-
- ///
- /// Gets a value indicating whether the runtime is executing on a Unix-like operating system.
- ///
- ///
- /// This is true for Linux, macOS, FreeBSD, and other Unix-like platforms.
- /// It is false only on Windows.
- ///
- internal static readonly bool IsUnix;
+ #endif
///
/// Initializes platform detection flags by querying .
@@ -72,11 +63,8 @@ static OsConstants()
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
-#if NET
+ #if NET
IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
- IsUnix = IsLinux || IsMacOS || IsFreeBSD;
-#else
- IsUnix = IsLinux || IsMacOS;
-#endif
+ #endif
}
}
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 d585e00cdd..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 (OsConstants.IsUnix)
+ 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 (OsConstants.IsUnix)
+ if (!OsConstants.IsWindows)
{
throw 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 1ba0d47f63..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 (OsConstants.IsUnix)
+ 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 (OsConstants.IsUnix)
+ if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException();
}
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 8278d4c23c..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 (OsConstants.IsUnix)
+ if (!OsConstants.IsWindows)
{
throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported);
}
From 49c3f7218c6a78879049b48ab0694de4fc8d89d4 Mon Sep 17 00:00:00 2001
From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com>
Date: Fri, 26 Jun 2026 12:32:10 -0300
Subject: [PATCH 3/4] Expose OsConstants flags as intrinsic properties for IL
trimming
Replace the static readonly fields and static constructor in OsConstants
with properties that directly return RuntimeInformation.IsOSPlatform(...).
The IL trimmer cannot analyze a static constructor, so the cached-field
design left OS-specific code (including Windows-only native dependencies)
in apps published for other platforms. Returning the intrinsic directly
lets the trimmer fold each guard to a constant and drop the dead branch.
Also document why the type exists (brevity and encapsulation), the
inline-guard requirement for trimming, and reconcile the stale remark
about JIT branch elision.
Addresses PR #4255 review feedback from @edwardneal.
---
.../Microsoft/Data/SqlClient/OsConstants.cs | 90 ++++++++++++-------
1 file changed, 56 insertions(+), 34 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
index 09f2c27960..71f2a3aef0 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
@@ -10,61 +10,83 @@ 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.
+/// 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 ever 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 the result of
+/// . This is deliberate: the IL trimmer (and the JIT)
+/// recognize these calls as intrinsics and can substitute a constant value for them at
+/// publish/compile time. Caching the results in static fields (for example, via a static
+/// constructor) would defeat this — 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.
///
- internal static readonly bool IsWindows;
+ internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
///
/// Gets a value indicating whether the runtime is executing on Linux.
///
- internal static readonly bool IsLinux;
+ internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
///
/// Gets a value indicating whether the runtime is executing on macOS.
///
- internal static readonly bool IsMacOS;
+ internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
#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
+ /// 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 readonly bool IsFreeBSD;
+ internal static bool IsFreeBSD => RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
#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
- }
}
From 6d509a8d18e1e89776677f7e5cb2ce2f05e628d2 Mon Sep 17 00:00:00 2001
From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com>
Date: Fri, 26 Jun 2026 12:54:39 -0300
Subject: [PATCH 4/4] Use OperatingSystem.Is*() on .NET for JIT-intrinsic OS
checks
On modern .NET, OperatingSystem.IsWindows()/IsLinux()/IsMacOS()/IsFreeBSD()
are JIT intrinsics that get constant-folded (and are recognized by the IL
trimmer), so switch the OsConstants properties to them under #if NET.
.NET Framework keeps RuntimeInformation.IsOSPlatform(...) since the
OperatingSystem platform-check helpers do not exist there.
This addresses the automated reviewer feedback that RuntimeInformation
.IsOSPlatform(...) misses the JIT constant-folding optimization on
net8/net9, while preserving the trimming correctness from the prior change.
Addresses PR #4255 review feedback.
---
.../Microsoft/Data/SqlClient/OsConstants.cs | 31 ++++++++++++++-----
1 file changed, 23 insertions(+), 8 deletions(-)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
index 71f2a3aef0..b496141693 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs
@@ -2,6 +2,7 @@
// 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;
@@ -13,16 +14,18 @@ namespace Microsoft.Data.SqlClient;
/// 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 ever needs special handling (for example, a different detection mechanism, a finer-grained
+/// 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 the result of
-/// . This is deliberate: the IL trimmer (and the JIT)
-/// recognize these calls as intrinsics and can substitute a constant value for them at
-/// publish/compile time. Caching the results in static fields (for example, via a static
-/// constructor) would defeat this — 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
+/// 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.
///
///
@@ -67,17 +70,29 @@ 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
///
@@ -87,6 +102,6 @@ internal static class OsConstants
/// 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 => RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
+ internal static bool IsFreeBSD => OperatingSystem.IsFreeBSD();
#endif
}