Skip to content

Moving platform guards out of System.Runtime.InteropServices #40111

@terrajobst

Description

@terrajobst

In .NET Core 1.x and 2.x days, we added very limited ability for customers to check which OS they were on. We originally didn't even expose Environment.OSVersion and we built a new API RuntimeInformation.IsOSPlatform(). The assumption was that doing OS checks is exclusively done for P/invoke or interoperating with native code, which is why RuntimeInformation was put in System.Runtime.InteropServices.

However, guarding calls to OS bindings or avoiding unsupported APIs in Blazor isn't really a P/invoke scenario. It's seems counterproductive to force these customers to import the System.Runtime.InteropServices namespace.

API Proposal

Since RuntimeInformation is a very small type today, the proposal is: don't add the API for version checks on RuntimeInformation but instead to add them to Environment.

Environment was the canonical place where people have performed OS checks in the past and are most likely to look for them moving forward. RuntimeInformation is a fairly recent type. And compared to Environment.OSVersion the RuntimeInformation type hasn't gained much adoption yet.

namespace System
{
    public static partial class Environment
    {
        // Primary

        public static bool IsOSPlatform(string platform);
        public static bool IsOSPlatformVersionAtLeast(string platform, int major, int minor = 0, int build = 0, int revision = 0);

        // Accelerators for platforms where versions don't make sense

        public static bool IsBrowser();
        public static bool IsFreeBSD();
        public static bool IsLinux();

        // Accelerators with version checks

        public static bool IsAndroid();
        public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);

        public static bool IsiOS();
        public static bool IsiOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);

        public static bool IsmacOS();
        public static bool IsmacOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);

        public static bool IstvOS();
        public static bool IstvOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);

        public static bool IswatchOS();
        public static bool IswatchOSVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);

        public static bool IsWindows();
        public static bool IsWindowsVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0);
    }
}

For the attributes, we'd like to put the core ones people are likely to apply to their own code in the same namespace as the guards, i.e. in System. We'll leave the base type and the TargetPlatformAttribute in System.Runtime.Versioning.

namespace System
{
    [AttributeUsage(AttributeTargets.Assembly |
                    AttributeTargets.Class |
                    AttributeTargets.Constructor |
                    AttributeTargets.Enum |
                    AttributeTargets.Event |
                    AttributeTargets.Field |
                    AttributeTargets.Method |
                    AttributeTargets.Module |
                    AttributeTargets.Property |
                    AttributeTargets.Struct,
                    AllowMultiple = true, Inherited = false)]
    public sealed class SupportedOSPlatformAttribute : OSPlatformAttribute
    {
        public SupportedOSPlatformAttribute(string platformName);
    }
  
    [AttributeUsage(AttributeTargets.Assembly |
                    AttributeTargets.Class |
                    AttributeTargets.Constructor |
                    AttributeTargets.Enum |
                    AttributeTargets.Event |
                    AttributeTargets.Field |
                    AttributeTargets.Method |
                    AttributeTargets.Module |
                    AttributeTargets.Property |
                    AttributeTargets.Struct,
                    AllowMultiple = true, Inherited = false)]
    public sealed class UnsupportedOSPlatformAttribute : OSPlatformAttribute
    {
        public UnsupportedOSPlatformAttribute(string platformName);
    }

    [AttributeUsage(AttributeTargets.Assembly |
                    AttributeTargets.Class |
                    AttributeTargets.Constructor |
                    AttributeTargets.Event |
                    AttributeTargets.Method |
                    AttributeTargets.Module |
                    AttributeTargets.Property |
                    AttributeTargets.Struct,
                    AllowMultiple=true, Inherited=false)]
    public sealed class ObsoletedInOSPlatformAttribute : OSPlatformAttribute
    {
        public ObsoletedInPlatformAttribute(string platformName);
        public ObsoletedInPlatformAttribute(string platformName, string message);
        public string Message { get; }
        public string Url { get; set; }
    }
}
namespace System.Runtime.Versioning
{
    public abstract class OSPlatformAttribute : Attribute
    {
        private protected OSPlatformAttribute(string platformName);
        public string PlatformName { get; }
    }

    [AttributeUsage(AttributeTargets.Assembly,
                    AllowMultiple=false, Inherited=false)]
    public sealed class TargetPlatformAttribute : OSPlatformAttribute
    {
        public TargetPlatformAttribute(string platformName);
    }
}

This would remove the following APIs:

 namespace System.Runtime.InteropServices
 {
     public static class RuntimeInformation
     {
         // Existing APIs
         public static string FrameworkDescription { get; }
         public static string OSArchitecture { get; }
         public static string OSDescription { get; }
         public static string ProcessArchitecture { get; }
         public static string RuntimeIdentifier { get; }
         public static bool IsOSPlatform(OSPlatform osPlatform);

-        // APIs proposed earlier for .NET 5.0
-        public static bool IsOSPlatformOrLater(string platformName);
-        public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major);
-        public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor);
-        public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build);
-        public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build, int revision);
-        public static bool IsOSPlatformEarlierThan(string platformName);
-        public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major);
-        public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor);
-        public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build);
-        public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build, int revision);
     }
 }

/cc @jeffhandley @eerhardt @adamsitnik @buyaa-n @mhutch

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.Runtime.InteropServicescode-analyzerMarks an issue that suggests a Roslyn analyzeruntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions