👀

PublishAot設定とRuntimeFeature.IsDynamicCodeCompiledプロパティについて

に公開

はじめに

.NETでNative AOTアプリとして発行する際に、.NETではPublishAotプロパティをプロジェクトファイル(.csproj)に設定する、もしくはMSBuild オプションとして追加した状態でdotnet publish -p:PublishAot=trueコマンド実行する必要があります。

csprojファイルでの設定例
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>

    <!--Native AOT設定-->
    <PublishAot>true</PublishAot>
  </PropertyGroup>
</Project>

今回はプロジェクトファイル(.csproj)に設定した場合にRuntimeFeature.IsDynamicCodeCompiledプロパティから取得できる値が変わったことについてまとめました。

確認環境

  • Visual Studio 2026
  • .NET 10 (10.0.101)

きっかけ

OSSの不具合動作として、Native AOTを用いたAOTコンパイルし、実行した際にエラーが発生するケースが見つかりました。そこで、RuntimeFeature.IsDynamicCodeCompiledプロパティを用いて、動的コードが実行できるかどうかを判定するように修正を行いました。

RuntimeFeature.IsDynamicCodeCompiledプロパティを使った分岐処理
object? Test()
{
    // RuntimeFeature.IsDynamicCodeCompiledでNative AOT かどうか判定
    if (!RuntimeFeature.IsDynamicCodeCompiled)
    {
        return null;
    }

    // 既存処理
    // リフレクションを用いた動的コード
}

RuntimeFeature.IsDynamicCodeCompiledプロパティ

まずRuntimeFeature.IsDynamicCodeCompiledプロパティについて説明すると、ランタイムが動的コードをコンパイルするかどうかを示すプロパティになり、普通にビルドして動作する際には true を返し、AOTコンパイルのように実行時の動的コード実行がサポートされていない環境では、false を返すプロパティです。

https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.compilerservices.runtimefeature.isdynamiccodecompiled?view=net-10.0

遭遇した動作

そして、RuntimeFeature.IsDynamicCodeCompiledプロパティを使った修正箇所について、Visual Studioにてデバッグ確認していたところ、ユニットテストとサンプルプロジェクトでRuntimeFeature.IsDynamicCodeCompiledプロパティの値が変わっていることに気づきました。
ユニットテストでは、RuntimeFeature.IsDynamicCodeCompiledプロパティがtrueとなりましたが、サンプルプロジェクトではfalseとなっていました。

RuntimeFeature.IsDynamicCodeCompiled
if (RuntimeFeature.IsDynamicCodeCompiled)
{
    // ユニットテストで実行した際にここを通る
    Console.WriteLine("RuntimeFeature.IsDynamicCodeCompiled = true");
}
else
{
    // サンプルプロジェクトだとこちらを通る
    Console.WriteLine("RuntimeFeature.IsDynamicCodeCompiled = false");
}

最初はユニットテストの環境が原因かと思いましたが特に問題なかったため、サンプルプロジェクトの構成を確認したところ、以下のようになっていました。

サンプルプロジェクトのcsprojファイル(例)
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsAotCompatible>true</IsAotCompatible>  

    <!--Native AOT設定-->
    <PublishAot>true</PublishAot>
  </PropertyGroup>

  .. 以下省略!
</Project>

そこには修正前には追加していなかったのですが、このときには<PublishAot>true</PublishAot>を追加していました。
おそらくが原因ということは明らかだったので、<PublishAot>true</PublishAot>の設定されている場合とされていない場合を比較し動作を確認したところ、どうやらPublishAotがtrueの場合、デバッグ時でもRuntimeFeature.IsDynamicCodeCompiledプロパティがfalseとして返す動作になっていました。
これより、PublishAotがRuntimeFeature.IsDynamicCodeCompiledプロパティと関係があるのは明らかだったので、続いてこれらの関係性について調べていきました。

PublishAot 設定

<PublishAot>は、公式ドキュメントだとNative AOTデプロイをする方法に記載されている説明文には以下の記述がありました。

このプロパティは、発行時にNative AOTコンパイルを有効にします。 また、ビルドと編集中の動的なコード使用状況分析も可能になります。 この設定は、発行の外部の動作を制御するため、コマンド ラインで渡すのではなく、プロジェクト ファイルに配置することをお勧めします。

https://learn.microsoft.com/ja-jp/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli

「この設定は、発行の外部の動作を制御するため...」というのがどこまで指しているのか判断が難しいですが、少なくとも動作を見ている限りでは、AOTコンパイルでの制約条件を適用した状態で実行しているように見えました。

私の認識では、dotnet publishコマンドで発行した際に有効になるような設定だと思っていたのですが、実行時の動作やASP.NET Coreのネイティブ AOTに関するドキュメントには、「ネイティブ AOT の発行を使うプロジェクトは、ローカルで実行するときに JIT コンパイルを使います。」とあるため、ローカル実行時では、AOTコンパイルでの制約条件が適用された状態でJITコンパイルされたものが動作していると思われます。

この設定により、発行時にネイティブ AOT コンパイルが有効になり、ビルド中と編集中に動的なコード使用状況分析が有効になります。 ネイティブ AOT の発行を使うプロジェクトは、ローカルで実行するときに JIT コンパイルを使います。 AOT アプリと JIT コンパイル アプリには次のような違いがあります。

https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/native-aot?view=aspnetcore-10.0

PublishAotとRuntimeFeature.IsDynamicCodeCompiledプロパティの関係

そして<PublishAot>を設定したプロジェクトをビルドした際に生成される[プロジェクト名].runtimeconfig.jsonの構成プロパティを確認すると"System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": falseが設定されていました。

[プロジェクト名].runtimeconfig.json(例)
{
  "runtimeOptions": {
    "tfm": "net10.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "10.0.0"
    },
    "configProperties": {
      "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true,
      "System.ComponentModel.DefaultValueAttribute.IsSupported": false,
      "System.ComponentModel.Design.IDesignerHost.IsSupported": false,
      "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false,
      "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false,
      "System.Data.DataSet.XmlSerializationIsSupported": false,
      "System.Diagnostics.Tracing.EventSource.IsSupported": false,
      "System.Linq.Enumerable.IsSizeOptimized": true,
      "System.Net.SocketsHttpHandler.Http3Support": false,
      "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
      "System.Resources.ResourceManager.AllowCustomResourceTypes": false,
      "System.Resources.UseSystemResourceKeys": false,
      // ここに設定されている!
      "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false,
      "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false,
      "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false,
      "System.Runtime.InteropServices.EnableCppCLIHostActivation": false,
      "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false,
      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
      "System.StartupHookProvider.IsSupported": false,
      "System.Text.Encoding.EnableUnsafeUTF7Encoding": false,
      "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false,
      "System.Threading.Thread.EnableAutoreleasePool": false,
      "System.Linq.Expressions.CanEmitObjectArrayDelegate": false
    }
  }
}

名前からこの値がおそらくRuntimeFeature.IsDynamicCodeCompiledプロパティが参照している設定だと推測できます。それを踏まえてRuntimeFeature.IsDynamicCodeCompiledプロパティの内部実装を確認します。

https://github.com/dotnet/runtime/blob/v10.0.1/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.NonNativeAot.cs#L8-L25

RuntimeFeature.IsDynamicCodeCompiledプロパティの実装は、通常のJIT環境であれば、IsDynamicCodeSupportedプロパティ経由でtrueを返し、機能スイッチとして定義されていればその機能スイッチの値に依存する処理になっています。
今回の場合は、System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupportedという[プロジェクト名].runtimeconfig.jsonに記載されている機能スイッチの値であるfalseが返却されることになります。

以上から、<PublishAot>true</PublishAot>は、ローカル実行時でもRuntimeFeature.IsDynamicCodeCompiledプロパティの値に影響与える設定ということがわかりました。

少し脱線しますが、dotnet publishコマンドでNative AOTコンパイルした場合には、以下のコードが通ると思われ、常にfalseが返却される処理になっています
まあ、当然といえば当然ですね。

https://github.com/dotnet/runtime/blob/v10.0.1/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.NativeAot.cs#L8-L15

まとめ

いままではコマンドのパラメータとして追加して実行していたため、あまり気にしたことはなかったのですが、色々調査していくと色々なものに影響を与えている設定ということが改めて把握できました。
Native AOTコンパイルでの動作は知らないことだらけなので、まだまだ学習する必要がありますね。

それでは今回はこのへんで。

参考文献

https://learn.microsoft.com/ja-jp/dotnet/core/runtime-config/
https://learn.microsoft.com/ja-jp/dotnet/core/whats-new/dotnet-9/runtime
https://github.com/dotnet/sdk/pull/29785
https://github.com/dotnet/runtime/issues/39806
https://github.com/dotnet/runtime/pull/80246

Discussion