🐡

[.NET] 実行時にOS・プラットフォーム等の実行環境による条件分岐をする

2021/08/08に公開

最近 RuntimeInformation という便利なクラスができました。ただしこれを普通に使うだけでは賄いきれない分岐を書きたいことがあります。

本記事では例として、実行時にランタイムが.NET Coreなのか否かを判定する処理を取り上げます。

.NETのクラスライブラリを作るに際し、それを .NET Framework からも.NET Core (.NET 5以降を含む) からも利用できるよう対応することは、まだ当面の間は求められることがあります。.NET Coreかどうかを実行時に判定する方法をメモします。

判定方法

今回も参照して話は終わってしまいます。こちらに答えがあります。
https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs#L24

この箇所だけ抽出すると以下のような感じです。

MyLib.cs
using System;
using System.Runtime.InteropServices;

namespace MyLib
{
    public static class MyLib
    {
        public static bool IsNetCore => Environment.Version.Major >= 5 || 
            RuntimeInformation.FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase);
    }
}

今回の例としてはこの1ファイルだけ持つクラスライブラリプロジェクトとし、TargetFrameworknetstandard2.0 に設定して、 .NET Framework 4.6.1以降でも.NET Core 2.0以降でも利用できるようにしておきます。

MyLib.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

使用例

xUnit.netのテストプロジェクトを隣に作って、上記MyLibを参照し、実行してみます。 #elif NET48 すなわち .NET Framework 4.8のところだけfalseで、ほか2つはtrueになり、希望を満たします。

MyLibTest.cs
using Xunit;

namespace MyLib.Tests
{
    public class MyLibTest
    {
        [Fact]
        public void IsNetCore()
        {
#if NET5_0_OR_GREATER
            Assert.True(MyLib.IsNetCore);
#elif NETCOREAPP3_1
            Assert.True(MyLib.IsNetCore);
#elif NET48
            Assert.False(MyLib.IsNetCore);
#endif
        }
    }
}

TargetPlatforms として3つの環境を指定しています。

MyLib.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net5.0;netcoreapp3.1;net48</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyLib\MyLib.csproj" />
  </ItemGroup>
</Project>

なおこのテストコードにあるように、コンパイル時に分岐するのでしたら定義済みのプリプロセッサシンボルが使えます。こちらに列挙されています。転載します。
https://github.com/dotnet/docs/blob/main/includes/preprocessor-symbols.md
https://github.com/dotnet/designs/blob/main/accepted/2020/or-greater-defines/or-greater-defines.md

Target Frameworks Symbols
.NET Framework NETFRAMEWORK, NET48, NET472, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, NET35, NET20, NET48_OR_GREATER, NET472_OR_GREATER, NET471_OR_GREATER, NET47_OR_GREATER, NET462_OR_GREATER, NET461_OR_GREATER, NET46_OR_GREATER, NET452_OR_GREATER, NET451_OR_GREATER, NET45_OR_GREATER, NET40_OR_GREATER, NET35_OR_GREATER, NET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, NETSTANDARD1_1, NETSTANDARD1_0, NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, NETSTANDARD1_6_OR_GREATER, NETSTANDARD1_5_OR_GREATER, NETSTANDARD1_4_OR_GREATER, NETSTANDARD1_3_OR_GREATER, NETSTANDARD1_2_OR_GREATER, NETSTANDARD1_1_OR_GREATER, NETSTANDARD1_0_OR_GREATER
.NET 6 (and .NET Core) NET6_0, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0, NET6_0_OR_GREATER, NET5_0_OR_GREATER, NETCOREAPP3_1_OR_GREATER, NETCOREAPP3_0_OR_GREATER, NETCOREAPP2_2_OR_GREATER, NETCOREAPP2_1_OR_GREATER, NETCOREAPP2_0_OR_GREATER, NETCOREAPP1_1_OR_GREATER, NETCOREAPP1_0_OR_GREATER

本記事は、何らかの理由でコンパイル時ではなく実行時に判定したい場合の方法を示しました。

その他のOS・プラットフォーム等による判定

参考にした本家dotnet/runtimeのリポジトリには、このような判定処理をまとめたクラスがあります。IsMacOsAppleSilicon, IsWindows10Version2004OrGreater など多彩な判定があるので活用できるでしょう。

Discussion