[C#] 実行中の .NET バージョンを取得する方法

2024-09-18 (水)

実行中のプロセスの .NET のバージョンを取得する方法です。.NET Core 以降と .NET Framework では異なる点があり、.NET Framework では注意が必要です。

環境

  • .NET 8.0.8 (SDK 8.0.400)
  • C# 12.0
  • Visual Studio 2022 Version 17.11.3
  • Windows 11 Pro 23H2 22631.4169

前提

.NET 8 から .NET Framework 4.8 まで、以下のように csproj に設定して検証しました。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;net48</TargetFrameworks>
  </PropertyGroup>

</Project>

取得結果

以下のプロパティを使用して、実行中の .NET バージョンを取得した結果です。

実行環境VersionFrameworkDescriptionTargetFrameworkName
.NET 88.0.8.NET 8.0.8.NETCoreApp,Version=v8.0
.NET 77.0.20.NET 7.0.20.NETCoreApp,Version=v7.0
.NET 66.0.33.NET 6.0.33.NETCoreApp,Version=v6.0
.NET 55.0.17.NET 5.0.17.NETCoreApp,Version=v5.0
.NET Core 3.13.1.32.NET Core 3.1.32.NETCoreApp,Version=v3.1
.NET Framework 4.8.14.0.30319.42000.NET Framework 4.8.9261.0.NETFramework,Version=v4.8

⚠️ .NET Framework は上記の方法は非推奨のため、正確に取得するにはレジストリを参照する方法が良いでしょう。
https://learn.microsoft.com/ja-jp/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed

ついでに、以下のプロパティの取得結果です。

実行環境RuntimeIdentifierProcessArchitectureOSArchitectureOSDescriptionOSVersion
.NET 8win-x64X64X64Microsoft Windows 10.0.22631Microsoft Windows NT 10.0.22631.0
.NET 7win10-x64X64X64Microsoft Windows 10.0.22631Microsoft Windows NT 10.0.22631.0
.NET 6win10-x64X64X64Microsoft Windows 10.0.22631Microsoft Windows NT 10.0.22631.0
.NET 5win10-x64X64X64Microsoft Windows 10.0.22631Microsoft Windows NT 10.0.22631.0
.NET Core 3.1N/AX64X64Microsoft Windows 10.0.22631Microsoft Windows NT 6.2.9200.0
.NET Framework 4.8.1N/AX64X64Microsoft Windows 10.0.22631Microsoft Windows NT 6.2.9200.0

Environment.Version, RuntimeInformation.FrameworkDescription について

.NET Core 3.1 以降では同じバージョン値が取得できています。
実際に取得するソースコードを抜粋すると、以下のようになっているようです。

.NET 5 以降のソースコード

  • typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion で取得します。
  • Version 型で取得するか、string".NET x.y.z" の形式で取得するかが異なります。
// .NET 5 以降の Environment.Version のコード抜粋
public static Version Version
{
    get
    {
        string? versionString = typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;

        ReadOnlySpan<char> versionSpan = versionString.AsSpan();

        // Strip optional suffixes
        int separatorIndex = versionSpan.IndexOfAny('-', '+', ' ');
        if (separatorIndex != -1)
            versionSpan = versionSpan.Slice(0, separatorIndex);

        // Return zeros rather then failing if the version string fails to parse
        return Version.TryParse(versionSpan, out Version? version) ? version : new Version();
    }
}
// .NET 5 以降の RuntimeInformation.FrameworkDescription のコード抜粋
private const string FrameworkName = ".NET";
private static string? s_frameworkDescription;

public static string FrameworkDescription
{
    get
    {
        if (s_frameworkDescription == null)
        {
            ReadOnlySpan<char> versionString = typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;

            // Strip the git hash if there is one
            int plusIndex = versionString.IndexOf('+');
            if (plusIndex >= 0)
            {
                versionString = versionString.Slice(0, plusIndex);
            }

            s_frameworkDescription = !versionString.Trim().IsEmpty ? $"{FrameworkName} {versionString}" : FrameworkName;
        }

        return s_frameworkDescription;
    }
}

.NET Core 3.1 のソースコード

  • typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion で取得します (.NET 5 以降同様)。
  • Version 型で取得するか、string".NET Core x.y.z" の形式で取得するかが異なります。
  • ただし、AppContext.GetData("FX_PRODUCT_VERSION") が設定されている場合は優先されます
// .NET Core 3.1 の Environment.Version のコード抜粋
public static Version Version
{
    get
    {
        // FX_PRODUCT_VERSION is expected to be set by the host
        string? versionString = (string?)AppContext.GetData("FX_PRODUCT_VERSION");

        if (versionString == null)
        {
            // Use AssemblyInformationalVersionAttribute as fallback if the exact product version is not specified by the host
            versionString = typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
        }

        ReadOnlySpan<char> versionSpan = versionString.AsSpan();

        // Strip optional suffixes
        int separatorIndex = versionSpan.IndexOfAny("-+ ");
        if (separatorIndex != -1)
            versionSpan = versionSpan.Slice(0, separatorIndex);

        // Return zeros rather then failing if the version string fails to parse
        return Version.TryParse(versionSpan, out Version? version) ? version : new Version();
    }
}
// .NET Core 3.1 の RuntimeInformation.FrameworkDescription のコード抜粋
private const string FrameworkName = ".NET Core";
private static string s_frameworkDescription;

public static string FrameworkDescription
{
    get
    {
        if (s_frameworkDescription == null)
        {
            string versionString = (string)AppContext.GetData("FX_PRODUCT_VERSION");

            if (versionString == null)
            {
                // Use AssemblyInformationalVersionAttribute as fallback if the exact product version is not specified by the host
                versionString = typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;

                // Strip the git hash if there is one
                int plusIndex = versionString.IndexOf('+');
                if (plusIndex != -1)
                    versionString = versionString.Substring(0, plusIndex);
            }

            s_frameworkDescription = $"{FrameworkName} {versionString}";
        }

        return s_frameworkDescription;
    }
}

.NET Framework 4.8.1 のソースコード

  • typeof(object).GetTypeInfo().Assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute))).Version で取得します。
  • ただし、Environment.Version は固定値です。
// .NET Framework 4.8.1 の Environment.Version のコード抜粋
public static Version Version => new Version(4, 0, 30319, 42000);
// .NET Framework 4.8.1 の RuntimeInformation.FrameworkDescription のコード抜粋
private static string s_frameworkDescription;

public static string FrameworkDescription
{
  get
  {
    if (RuntimeInformation.s_frameworkDescription == null)
      RuntimeInformation.s_frameworkDescription = ".NET Framework " + ((AssemblyFileVersionAttribute)typeof(object).GetTypeInfo().Assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute))).Version;
    return RuntimeInformation.s_frameworkDescription;
  }
}

.NET Framework では取得方法が大きく異なっていることが分かります。

ちなみに typeof(object).Assembly.Location は、以下の場所になります。

C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.8\System.Private.CoreLib.dll
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.20\System.Private.CoreLib.dll
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.33\System.Private.CoreLib.dll
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.17\System.Private.CoreLib.dll
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.32\System.Private.CoreLib.dll
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll

AppContext.TargetFrameworkName について

他に AppDomainSetup.TargetFrameworkName でも取得できますが、結果は全く同じでした。

AppContext.TargetFrameworkName の取得するソースコードを抜粋すると、以下のようになっているようです。

.NET 5 以降および .NET Core 3.1 のソースコード

// .NET 5 以降および .NET Core 3.1 の AppContext.TargetFrameworkName のコード抜粋
public static string? TargetFrameworkName =>
    // The Target framework is not the framework that the process is actually running on.
    // It is the value read from the TargetFrameworkAttribute on the .exe that started the process.
    Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;

.NET Framework 4.8.1 のソースコード

// .NET Framework 4.8.1 の AppContext.TargetFrameworkName のコード抜粋
public static class AppContext
{
    public static string TargetFrameworkName
    {
        // 1. AppDomainSetup.TargetFrameworkName プロパティを参照している
        get => AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName;
    }
}

public sealed class AppDomainSetup : IAppDomainSetup
{
    [OptionalField(VersionAdded = 5)]
    private string _TargetFrameworkName;

    // 2. ここにセットされる値は AppDomain.GetTargetFrameworkName() で取得している
    public string TargetFrameworkName
    {
        get => this._TargetFrameworkName;
        set => this._TargetFrameworkName = value;
    }
}

public sealed class AppDomain
{
    private AppDomainSetup _FusionStore;

    // 3. ここで実際に取得する
    internal String GetTargetFrameworkName()
    {
        String targetFrameworkName = _FusionStore.TargetFrameworkName;

        if (targetFrameworkName == null && IsDefaultAppDomain() && !_FusionStore.CheckedForTargetFrameworkName)
        {
            // This should only be run in the default appdomain.  All other appdomains should have
            // values copied from the default appdomain and/or specified by the host.
            Assembly assembly = Assembly.GetEntryAssembly();
            if (assembly != null)
            {
                TargetFrameworkAttribute[] attrs = (TargetFrameworkAttribute[])assembly.GetCustomAttributes(typeof(TargetFrameworkAttribute));
                if (attrs != null && attrs.Length > 0)
                {
                    Contract.Assert(attrs.Length == 1);
                    targetFrameworkName = attrs[0].FrameworkName;
                    _FusionStore.TargetFrameworkName = targetFrameworkName;
                }
            }
            _FusionStore.CheckedForTargetFrameworkName = true;
        }

        return targetFrameworkName;
    }
}
  • いずれの .NET バージョンも取得方法は同じで、Assembly.GetEntryAssembly()TargetFrameworkAttribute を取得します。
  • Assembly.GetEntryAssembly() は、通常は .NET プロセスが開始されたアセンブリを取得しますがが、null になる場合もある点に注意が必要です。
  • 例えば、pythonnetCOM などの相互運用で呼び出された場合は null になります。
  • また .NET 9 では Assembly.SetEntryAssembly() メソッドも追加されているため、変更される可能性があります。

感謝

2024-09-18 (水)