--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Runtime.InteropServices
+{
+ public static partial class RuntimeInformation
+ {
+ /// <summary>
+ /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release.
+ /// </summary>
+ /// <param name="platformName">OS name concatenated with a version number.</param>
+ /// <remarks>The version number must contain at least major and minor numbers separated with a dot.
+ /// Example: "ios14.0" is OK, "ios14" is NOT OK.</remarks>
+ public static bool IsOSPlatformOrLater(string platformName)
+ {
+ (OSPlatform platform, Version version) = Parse(platformName);
+
+ return IsOSPlatformOrLater(platform, version.Major, version.Minor, version.Build, version.Revision);
+ }
+
+ /// <summary>
+ /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major)
+ => IsOSPlatform(osPlatform) && Environment.OSVersion.Version.Major >= major;
+
+ /// <summary>
+ /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor)
+ => IsOSPlatform(osPlatform) && IsOSVersionOrLater(major, minor, int.MinValue, int.MinValue);
+
+ /// <summary>
+ /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build)
+ => IsOSPlatform(osPlatform) && IsOSVersionOrLater(major, minor, build, int.MinValue);
+
+ /// <summary>
+ /// Check for the OS with a >= version comparison. Used to guard APIs that were added in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build, int revision)
+ => IsOSPlatform(osPlatform) && IsOSVersionOrLater(major, minor, build, revision);
+
+ /// <summary>
+ /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release.
+ /// </summary>
+ /// <param name="platformName">OS name concatenated with a version number.</param>
+ /// <remarks>The version number must contain at least major and minor numbers separated with a dot.
+ /// Example: "ios14.0" is OK, "ios14" is NOT OK.</remarks>
+ public static bool IsOSPlatformEarlierThan(string platformName)
+ {
+ (OSPlatform platform, Version version) = Parse(platformName);
+
+ return IsOSPlatformEarlierThan(platform, version.Major, version.Minor, version.Build, version.Revision);
+ }
+
+ /// <summary>
+ /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major)
+ => IsOSPlatform(osPlatform) && Environment.OSVersion.Version.Major < major;
+
+ /// <summary>
+ /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor)
+ => IsOSPlatform(osPlatform) && !IsOSVersionOrLater(major, minor, int.MinValue, int.MinValue);
+
+ /// <summary>
+ /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build)
+ => IsOSPlatform(osPlatform) && !IsOSVersionOrLater(major, minor, build, int.MinValue);
+
+ /// <summary>
+ /// Check for the OS with a < version comparison. Used to guard APIs that were obsoleted or removed in the given OS release.
+ /// </summary>
+ public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build, int revision)
+ => IsOSPlatform(osPlatform) && !IsOSVersionOrLater(major, minor, build, revision);
+
+ private static bool IsOSVersionOrLater(int major, int minor, int build, int revision)
+ {
+ Version current = Environment.OSVersion.Version;
+ if (current.Major != major)
+ {
+ return current.Major > major;
+ }
+ if (current.Minor != minor)
+ {
+ return current.Minor > minor;
+ }
+ if (current.Build != build)
+ {
+ return current.Build > build;
+ }
+
+ return current.Revision >= revision;
+ }
+
+ private static (OSPlatform, Version) Parse(string platformName)
+ {
+ if (platformName == null)
+ {
+ throw new ArgumentNullException(nameof(platformName));
+ }
+ if (platformName.Length == 0)
+ {
+ throw new ArgumentException(SR.Argument_EmptyValue, nameof(platformName));
+ }
+
+ // iterate from the begining, as digits in the middle of the names are not supported by design
+ for (int i = 0; i < platformName.Length; i++)
+ {
+ if (char.IsDigit(platformName[i]))
+ {
+ if (i > 0 && Version.TryParse(platformName.AsSpan(i), out Version? version))
+ {
+ return (OSPlatform.Create(platformName.Substring(0, i)), version);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ throw new ArgumentException(SR.Format(SR.Argument_InvalidPlatfromName, platformName), nameof(platformName));
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Runtime.InteropServices.RuntimeInformationTests
+{
+ public class PlatformVersionTests
+ {
+ public static IEnumerable<object[]> AllKnownOsPlatforms()
+ {
+ yield return new object[] { OSPlatform.Windows };
+ yield return new object[] { OSPlatform.Linux };
+ yield return new object[] { OSPlatform.OSX };
+ yield return new object[] { OSPlatform.Browser };
+ }
+
+ [Fact]
+ public void IsOSPlatformOrLater_Null_ThrowsArgumentNullExceptionWithArgumentName()
+ {
+ Assert.Throws<ArgumentNullException>("platformName", () => RuntimeInformation.IsOSPlatformOrLater(null));
+ }
+
+ [Fact]
+ public void IsOSPlatformOrLater_Empty_ThrowsArgumentNullExceptionWithArgumentName()
+ {
+ Assert.Throws<ArgumentException>("platformName", () => RuntimeInformation.IsOSPlatformOrLater(string.Empty));
+ }
+
+ [Theory]
+ [InlineData("ios")] // missing version number
+ [InlineData("ios14")] // ios14.0 is fine, ios14 is not: https://github.com/dotnet/runtime/pull/39005#discussion_r452541491
+ [InlineData("ios14.0.0.0.0.0")] // too many numbers
+ [InlineData("ios14.0.")] // version should not end with dot (but OS name could potentially end with dot, imagine "NET.")
+ [InlineData("numbers1.2inplatformname1.2")] // numbers in platform names are not supported https://github.com/dotnet/runtime/pull/39005#discussion_r452644601
+ public void IsOSPlatformOrLater_InvalidVersionNumber_ThrowsArgumentExceptionWithArgumentName(string platformName)
+ {
+ Assert.Throws<ArgumentException>("platformName", () => RuntimeInformation.IsOSPlatformOrLater(platformName));
+ }
+
+ [Theory]
+ [MemberData(nameof(AllKnownOsPlatforms))]
+ public void IsOSPlatformOrLater_ReturnsTrue_ForCurrentOS(OSPlatform osPlatform)
+ {
+ // IsOSPlatformOrLater("xyz1.2.3.4") running as "xyz1.2.3.4" should return true
+
+ bool isCurrentPlatfom = RuntimeInformation.IsOSPlatform(osPlatform);
+ Version current = Environment.OSVersion.Version;
+
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{current}"));
+
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major));
+
+ if (current.Minor >= 0)
+ {
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major, current.Minor));
+
+ if (current.Build >= 0)
+ {
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major, current.Minor, current.Build));
+
+ if (current.Revision >= 0)
+ {
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, current.Major, current.Minor, current.Build, current.Revision));
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(AllKnownOsPlatforms))]
+ public void IsOSPlatformOrLater_ReturnsFalse_ForNewerVersionOfCurrentOS(OSPlatform osPlatform)
+ {
+ // IsOSPlatformOrLater("xyz11.0") running as "xyz10.0" should return false
+
+ Version currentVersion = Environment.OSVersion.Version;
+
+ Version newer = new Version(currentVersion.Major + 1, 0);
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}"));
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major));
+
+ newer = new Version(currentVersion.Major, currentVersion.Minor + 1);
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}"));
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major, newer.Minor));
+
+ newer = new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build + 1);
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}"));
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major, newer.Minor, newer.Build));
+
+ newer = new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build, currentVersion.Revision + 1);
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{newer}"));
+ Assert.False(RuntimeInformation.IsOSPlatformOrLater(osPlatform, newer.Major, newer.Minor, newer.Build, newer.Revision));
+ }
+
+ [Theory]
+ [MemberData(nameof(AllKnownOsPlatforms))]
+ public void IsOSPlatformOrLater_ReturnsTrue_ForOlderVersionOfCurrentOS(OSPlatform osPlatform)
+ {
+ // IsOSPlatformOrLater("xyz10.0") running as "xyz11.0" should return true
+
+ bool isCurrentPlatfom = RuntimeInformation.IsOSPlatform(osPlatform);
+ Version current = Environment.OSVersion.Version;
+
+ Version older = new Version(current.Major - 1, 0);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major));
+
+ if (current.Minor > 0)
+ {
+ older = new Version(current.Major, current.Minor - 1);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major, older.Minor));
+ }
+
+ if (current.Build > 0)
+ {
+ older = new Version(current.Major, current.Minor, current.Build - 1);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major, older.Minor, older.Build));
+ }
+
+ if (current.Revision > 0)
+ {
+ older = new Version(current.Major, current.Minor, current.Build, current.Revision - 1);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater($"{osPlatform}{older}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformOrLater(osPlatform, older.Major, older.Minor, older.Build, older.Revision));
+ }
+ }
+
+ [Fact]
+ public void IsOSPlatformEarlierThan_Null_ThrowsArgumentNullExceptionWithArgumentName()
+ {
+ Assert.Throws<ArgumentNullException>("platformName", () => RuntimeInformation.IsOSPlatformEarlierThan(null));
+ }
+
+ [Fact]
+ public void IsOSPlatformEarlierThan_Empty_ThrowsArgumentNullExceptionWithArgumentName()
+ {
+ Assert.Throws<ArgumentException>("platformName", () => RuntimeInformation.IsOSPlatformEarlierThan(string.Empty));
+ }
+
+ [Theory]
+ [InlineData("ios")] // missing version number
+ [InlineData("ios14")] // ios14.0 is fine, ios14 is not: https://github.com/dotnet/runtime/pull/39005#discussion_r452541491
+ [InlineData("ios14.0.0.0.0.0")] // too many numbers
+ [InlineData("ios14.0.")] // version should not end with dot (but OS name could potentially end with dot, imagine "NET.")
+ [InlineData("numbers1.2inplatformname1.2")] // numbers in platform names are not supported https://github.com/dotnet/runtime/pull/39005#discussion_r452644601
+ public void IsOSPlatformEarlierThan_InvalidVersionNumber_ThrowsArgumentExceptionWithArgumentName(string platformName)
+ {
+ Assert.Throws<ArgumentException>("platformName", () => RuntimeInformation.IsOSPlatformEarlierThan(platformName));
+ }
+
+ [Theory]
+ [MemberData(nameof(AllKnownOsPlatforms))]
+ public void IsOSPlatformEarlierThan_ReturnsFalse_ForCurrentOS(OSPlatform osPlatform)
+ {
+ // IsOSPlatformEarlierThan("xyz1.2.3.4") running as "xyz1.2.3.4" should return false
+
+ Version current = Environment.OSVersion.Version;
+
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{current}"));
+
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major));
+
+ if (current.Minor >= 0)
+ {
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major, current.Minor));
+
+ if (current.Build >= 0)
+ {
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major, current.Minor, current.Build));
+
+ if (current.Revision >= 0)
+ {
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, current.Major, current.Minor, current.Build, current.Revision));
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(AllKnownOsPlatforms))]
+ public void IsOSPlatformEarlierThan_ReturnsTrue_ForNewerVersionOfCurrentOS(OSPlatform osPlatform)
+ {
+ // IsOSPlatformEarlierThan("xyz11.0") running as "xyz10.0" should return true
+
+ bool isCurrentPlatfom = RuntimeInformation.IsOSPlatform(osPlatform);
+ Version current = Environment.OSVersion.Version;
+
+ Version newer = new Version(current.Major + 1, 0);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major));
+
+ newer = new Version(current.Major, current.Minor + 1);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major, newer.Minor));
+
+ newer = new Version(current.Major, current.Minor, current.Build + 1);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major, newer.Minor, newer.Build));
+
+ newer = new Version(current.Major, current.Minor, current.Build, current.Revision + 1);
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{newer}"));
+ Assert.Equal(isCurrentPlatfom, RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, newer.Major, newer.Minor, newer.Build, newer.Revision));
+ }
+
+ [Theory]
+ [MemberData(nameof(AllKnownOsPlatforms))]
+ public void IsOSPlatformEarlierThan_ReturnsFalse_ForOlderVersionOfCurrentOS(OSPlatform osPlatform)
+ {
+ // IsOSPlatformEarlierThan("xyz10.0") running as "xyz11.0" should return false
+
+ Version current = Environment.OSVersion.Version;
+
+ Version older = new Version(current.Major - 1, 0);
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}"));
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major));
+
+ if (current.Minor > 0)
+ {
+ older = new Version(current.Major, current.Minor - 1);
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}"));
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major, older.Minor));
+ }
+
+ if (current.Build > 0)
+ {
+ older = new Version(current.Major, current.Minor, current.Build - 1);
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}"));
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major, older.Minor, older.Build));
+ }
+
+ if (current.Revision > 0)
+ {
+ older = new Version(current.Major, current.Minor, current.Build, current.Revision - 1);
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan($"{osPlatform}{older}"));
+ Assert.False(RuntimeInformation.IsOSPlatformEarlierThan(osPlatform, older.Major, older.Minor, older.Build, older.Revision));
+ }
+ }
+ }
+}