.NET 10 Preview 4 単一ファイルでの実行をためしてみた

## はじめに

Microsoft Buildのセッションで、`No projects just C# with dotnet run app.cs | DEM518` という興味深いセッションがあったので、テスト用コンテナを使ってどう動くかを確認していきます。

https://build.microsoft.com/en-US/sessions/3f17af10-3904-4074-aab9-58cdedfd0bc8

## とりあえず実行してみる

まずは、.NET 10.0 のコンテナをインタラクティブモードで起動します。

```sh
❯ docker run -it -p 8080:8080 --rm mcr.microsoft.com/dotnet/sdk:10.0-preview
root@c89d473e5898:/# dotnet --list-sdks
10.0.100-preview.4.25258.110 [/usr/share/dotnet/sdk]
```

ホームディレクトリに移動し、ソースコードを作って実行します。

```sh
root@c89d473e5898:/# cd
root@c89d473e5898:/# cat <<-EOF > hello.cs
Console.WriteLine("Hello.NET10");
EOF
root@c89d473e5898:/# dotnet run hello.cs
Hello.NET10
```

ちゃんと実行されましたね。

## パッケージを参照する

`#:package` でNuGetのパッケージを参照できます。
セッションと同様に、Humanizerを参照して使ってみます。

```sh
root@c89d473e5898:/# cat <<-EOF > hello.cs
#:package Humanizer@2.*
using Humanizer;
Console.WriteLine($"Hello.NET10Now={DateTimeOffset.Now}".Humanize());
EOF
root@c89d473e5898:~# dotnet run hello.cs
Hello NET 10 now 05 26 2025 07 12 29 00 00
```

## shebangを指定して実行する

Linux環境であればshebangが使えます。標準のDockerであれば`/usr/bin`にdotnetコマンドが配置されているので`#!`でパスを設定し、ファイルに実行環境を付与して実行します。

```sh
root@c89d473e5898:/# cat <<-EOF > hello.cs
#!/usr/bin/dotnet run
#:package Humanizer@2.*
using Humanizer;
Console.WriteLine($"Hello.NET10Now={DateTimeOffset.Now}".Humanize());
EOF
root@c89d473e5898:~# chmod +x ./hello.cs
root@c89d473e5898:~# ./hello.cs
Hello NET 10 now 05 26 2025 07 19 46 00 00
```

## SDKを指定してWebをホストする

もちろんASP.NET Coreも実行できます。
`#:sdk`でSDKを指定できるので、ASP.NET Coreであれば、`Microsoft.NET.Sdk.Web`を指定します。

```sh
root@c89d473e5898:~# cat <<-EOF > web.cs
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.Sdk.Web
#:package Humanizer@2.*
using Humanizer;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => $"Hello.NET10Now={DateTimeOffset.Now}".Humanize());
app.Run();
EOF
```

同様に実行権限を付与して実行します。

```sh
root@c89d473e5898:~# chmod +x web.cs
root@c89d473e5898:~# ./web.cs
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:8080
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /root
```

Windows側からWebブラウザーで `localhost:8080` に接続すると、期待通りのレスポンスが返却されることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/70fcc9e6-1982-48f3-ae08-0683b6104717.png)

## プロジェクトに変換する

`dotnet project` サブコマンドでプロジェクトに変換できます。

```
root@c89d473e5898:~# ls
web.cs
root@c89d473e5898:~# dotnet project convert web.cs
root@c89d473e5898:~# ls
web
root@c89d473e5898:~# cd web/
root@c89d473e5898:~/web# ls
web.cs  web.csproj
```

ソースコードからパッケージの参照やシェバン、SDK指定がなくなり、

```web/web.cs
using Humanizer;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => $"Hello.NET10Now={DateTimeOffset.Now}".Humanize());
app.Run();
```

プロジェクトファイルに参照が追加されています。

```web/web.csproj
﻿<Project Sdk="Microsoft.NET.Sdk.Web">

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

  <ItemGroup>
    <PackageReference Include="Humanizer" Version="2.*" />
  </ItemGroup>

</Project>
```

## おわりに

実際この機能をどこまで使うかなぁという気はしますが、ちょっとしたツールを作るのには便利な気がしますね！