C# .NET 8.0+Native AOTでコンソールアプリを作成する

# はじめに

.NET 8.0+Native AOTでWindowsコンソールアプリを作成する方法です。  

普段は .NET Framework 4.8.xを使用していますが .NET 8.0+Native AOTを使えば簡単に[JIT](https://ja.wikipedia.org/wiki/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9)
不要のコンソールアプリが作成できます。手元の環境はWindows10 x64です。  

## Native AOT(Ahead-Of-Time)について

[Native AOT deployment overview - .NET | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/core/deploying/native-aot/?tabs=net7%2Cwindows)  

>Publishing your app as Native AOT produces an app that's self-contained and that has been ahead-of-time (AOT) compiled to native code.

アプリをNative AOTとして公開するとネイティブ コードに[事前コンパイル](https://ja.wikipedia.org/wiki/%E4%BA%8B%E5%89%8D%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9)されるため[.NET ランタイム](https://dotnet.microsoft.com/ja-jp/download/dotnet/8.0)のない環境でも実行することができます。  

.NET Framework 4.8.xは[Windows10/11に既定でインストール](https://learn.microsoft.com/ja-jp/dotnet/framework/migration-guide/versions-and-dependencies)されており、追加でランタイムを入れる必要がない一方で、起動が遅いという欠点があります。[AOT](https://learn.microsoft.com/ja-jp/dotnet/standard/glossary#aot)であればその欠点を補うことができます。  

# 開発環境

Native AOT開発では[「C++によるデスクトップ開発」を含むVisual Studio 2022が必要](https://learn.microsoft.com/ja-jp/dotnet/core/deploying/native-aot/?tabs=net7%2Cwindows#prerequisites)になります。  

## C++によるデスクトップ開発

Visual Studio Installerで「C++によるデスクトップ開発」にチェックを入れます。コンソールアプリであれば[VS Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code) + [C#拡張機能](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp)を使用したコーディングのほうが軽量でよいと思いますがVisual Studioでコーディングする場合は、「.NET デスクトップ開発」にもチェックを入れます。  
![vsi1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/a13c7f23-b262-9f09-260a-1de69d7dd02b.png)  

## .NET SDK

個別のコンポーネントで .NET SDKにチェックを入れます。SDKにチェックを入れると.NET8.0ランタイムも同時にインストールされます。上記で「.NET デスクトップ開発」にもチェックを入れた場合はその時点でチェックが入りますので本操作は不要です。  
![vsi2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/074eb13e-9338-0de9-4723-58e80f527110.png)  

# アプリの作成

[Native AOT開発](https://learn.microsoft.com/ja-jp/dotnet/core/deploying/native-aot/?tabs=net7%2Cwindows#publish-native-aot-using-the-cli)のページに"GitHubにサンプルがあります"と書かれているのでそちらを参考にします。  

* [samples/core/nativeaot/HelloWorld at main · dotnet/samples · GitHub](https://github.com/dotnet/samples/tree/main/core/nativeaot/HelloWorld)  

1. プロジェクトの作成  
    コマンド プロンプトで以下のコマンドを実行することでHelloWorldというフォルダが作成され、`Program.cs`とプロジェクトファイル`HelloWorld.csproj`が作成されます。
    ```
    dotnet new console -o HelloWorld --aot
    ```
    [既定](https://learn.microsoft.com/ja-jp/dotnet/core/tools/dotnet-new-sdk-templates#console)では[最上位レベルのステートメント](https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/program-structure/top-level-statements)が有効です。無効にする場合は、  
    ```
    dotnet new console -o HelloWorld --use-program-main --aot
    ```
1. コードの記述  
    `Program.cs`にコードを記述します。デフォルトでは`Hello, World!`と表示するコードになっています。  
    ```cs
    // See https://aka.ms/new-console-template for more information
    Console.WriteLine("Hello, World!");
    ```
1. 公開  
    `HelloWorld`フォルダをコマンド プロンプトで開いて、以下のコマンドを実行すると、`.\bin\Release\net8.0\win-x64\publish`内に`HelloWorld.exe`が作成されます。  
    ```
    dotnet publish
    ```
1. 実行  
    コマンドプロンプト上で`HelloWorld.exe`を実行すると`Hello, World!`と表示されます。  

# 補足

## .NET 8.0+Native AOTでの開発にあたり

普段は .NET Framework 4.8.xを使用しているため、.NET 8.0+Native AOTで開発する上での覚え書きを記載しておきます。  

* .NET Framework 4.8.xではなく .NET 8.0を使用することでより新しいバージョンのC#で開発することができます。C#のバージョンについては[C# 言語の既定のバージョン](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/language-versioning#defaults)と[C# の歴史](https://learn.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-version-history)を参照。C#については [C# 言語リファレンス](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/)や[概要 - A tour of C#](https://learn.microsoft.com/ja-jp/dotnet/csharp/tour-of-csharp/overview)を参照。    

* 引数を参照する場合は[`args`変数](https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/program-structure/top-level-statements#args)を使用します。`args[0]`は自身のパスではなく引数1個目です。  

* 以下の [using ディレクティブが自動的に追加](https://learn.microsoft.com/ja-jp/dotnet/core/tutorials/top-level-templates#implicit-using-directives)されます。  
    * `using System;`  
    * `using System.IO;`  
    * `using System.Collections.Generic;`  
    * `using System.Linq;`  
    * `using System.Net.Http;`  
    * `using System.Threading;`  
    * `using System.Threading.Tasks;`  

    無効にする場合は`*csproj`ファイル（上記の例では`HelloWorld.csproj`）を開いて`<ImplicitUsings>enable</ImplicitUsings>`の箇所を`<ImplicitUsings>disable</ImplicitUsings>`にします。  

* 自身のパスを取得するために`System.Reflection.Assembly.GetExecutingAssembly().Location`を使うとエラーになります。  
    ```cs
    //自身のパスを取得
    string appPath = System.Reflection.Assembly.GetExecutingAssembly().Location;//エラー
    string appPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;//代替例1
    string appPath = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);//代替例2。コマンドラインで使用する場合、拡張子をつけないで呼び出すとここでも拡張子なしとなる。
    //自身のあるフォルダ
    string appFolder = System.AppContext.BaseDirectory;
    ```

* 起動時にコマンドプロンプトを出す必要がない/出したくない場合は`*csproj`ファイルの`OutputType`を`Exe`から`WinExe`に変更します。

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

    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <PublishAot>true</PublishAot>
        <InvariantGlobalization>true</InvariantGlobalization>
    </PropertyGroup>

    </Project>
    ```

    例えば、起動時に自身のパスを表示するだけのプログラムは、  

    ```cs:Program.cs
    using System.Runtime.InteropServices;

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    static extern int MessageBoxW(nint hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpText, [MarshalAs(UnmanagedType.LPWStr)] string lpCaption, uint uType);

    string appPath = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
    MessageBoxW(nint.Zero, appPath, "MyPath", 0);
    ```

## Visual Studio 2022でコーディング

[環境設定](#cによるデスクトップ開発)で「.NET デスクトップ開発」にもチェックを入れVisual Studioでコーディングする場合は、  

1. Visual Studio 2022を起動し、「新しいプロジェクトの作成(N)」を押します。  
    ![vs01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/21fc41ca-15fc-0d3f-4e14-a5b78b85cf32.png)  
1. 「コンソール アプリ」を選んで次へ。  
    ![vs02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/093a1923-63b7-9412-7ba8-7046f5b6cb7f.png)  
1. プロジェクトの名前と保存場所を設定し次へ。  
    ![vs03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/dc1dc07f-faab-ab19-3e01-38cec9e2dbe0.png)  
1. フレームワークとして「.NET 8.0」を選択し、「native AOT発行を有効にする」にチェックを入れて「作成」を押します。  
    ![vs04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/780244db-5415-e77e-068b-0b16ae7d6096.png)  
1. コードを記述します。  
1. プロジェクトを右クリックして「発行」を選択します。発行メニューがない場合は、一度Visual Studio Installerで「.NET デスクトップ開発」のみを選択状態にして変更。その後再度Visual Studio Installerを立ち上げて「C++によるデスクトップ開発」にチェックを入れて変更を実施してみてください。  
    ![vs05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/87abb020-67b4-e53e-7f83-d9c83384f146.png)  
1. フォルダを選択し次へ。  
    ![vs06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/da40c1d4-49c7-a5b8-cf76-c9f32de1bdb1.png)  
1. フォルダを選択し次へ。  
    ![vs07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/579f372f-3d0f-a444-ae4c-527f6ba0a60c.png)  
1. 出力先を設定し「完了」を押します。  
    ![vs08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/6e63fafc-72db-699b-8b2d-6bb7efdae7e5.png)  
1. 「閉じる」を押します。  
    ![vs09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/de5d38c7-e063-603b-d901-cfb2924ec6fc.png)  
1. 「すべての設定を表示」を押します。  
    ![vs10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/e4b82255-6f42-db2c-65f9-31526d39cabc.png)  
1. 「配置モード」と「ターゲット ランタイム」を選択して「保存」を押します。  
    ![vs11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/eaee9698-6fbd-3b9a-5e6f-bc9ce8264e5f.png)  
1. 「発行」を押します。処理が完了したら、出力先にアプリがあることを確認します。  
    ![vs12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/11473c65-d512-cd01-3171-09dbcc2ded6a.png)