「トップレベルステートメントを使いたくない。昔の static void Main() に戻したい」。.NET 6 以降のテンプレートやチュートリアルに触れると多くの方が抱く疑問です。本記事は“オフにする”設定の有無から、新規・既存プロジェクトの具体的な対処、ImplicitUsings との違い、そしてチーム運用のベストプラクティスまで、現場で迷わないための実践的な手順をまとめます。
C# トップレベルステートメントとは何か(要点の再整理)
トップレベルステートメントは C# 9 以降で導入された構文糖(シンタックスシュガー)です。Program クラスや Main メソッドを書かずに、ファイル先頭に直接処理を書けます。コンパイラは内部的に以下のようなエントリポイントを合成します。
// Program.cs(トップレベル)
using System;
Console.WriteLine("Hello, world!");
// <-- コンパイラが暗黙に Program.Main(...) を作る
この“合成”は純粋な構文糖です。書かなければ有効になりません。つまり、無効化フラグは不要で、従来どおり Program クラス+ Main を定義すればそのまま従来構造で動きます。
「無効化」ではなく「使わない」——設計の考え方
| 要点 | 解説 |
|---|---|
| コンパイラの無効化スイッチはない | トップレベルステートメントは“選択的な構文”。採用しなければ従来形式のままです。 |
| テンプレートで最初から避ける | 新規作成時に Visual Studio のチェック、もしくは CLI のテンプレート引数で回避できます。 |
| 既存コードは書き換えだけで戻せる | トップレベル部分を削除し、Program.Main() を明示します。特別な MSBuild 設定は不要。 |
| ImplicitUsings は別機能 | <ImplicitUsings>disable</ImplicitUsings> は自動 using の有効/無効。トップレベルの採否とは無関係です。 |
| 言語バージョンで封じる(補足) | <LangVersion>8.0</LangVersion> など C# 8 以下に固定すれば構文自体を受け付けませんが、新機能も使えなくなります。 |
新規プロジェクトでトップレベルステートメントを使わない方法
Visual Studio(日本語 UI)の手順
- 「新しいプロジェクト」から目的のテンプレート(例:コンソール アプリ)を選択。
- 「追加情報」画面で 「トップレベル ステートメントを使用しない」 にチェック。
- 作成後の
Program.csは従来のProgramクラス+Mainを含む形で生成されます。
※ UI ラベルは Visual Studio のバージョン/言語により表記がわずかに異なる場合があります。
CLI(.NET SDK)の最短コマンド
コンソール アプリの場合は以下で Program.Main ありのテンプレートを生成できます。
dotnet new console --use-program-main -o HelloApp
生成される Program.cs は従来型です。
using System;
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, world!");
}
}
Web/Worker など他テンプレートでは、初期状態がトップレベル風の Program.cs(新ホスティングモデル)になることがあります。その場合でも、自前で Program.Main を明示するのが最も確実です。テンプレート固有のスイッチ有無に依存しないため、チーム標準化にも向きます。
既存プロジェクトを Main 方式に戻す(手順とコツ)
最短 3 ステップ
- トップレベルコードを発見する(
Program.cs先頭にConsole.WriteLine(...);などが直接書かれている)。 - そのブロックを
Mainに移す。必要ならasync Task Mainに。 - ファイル先頭からトップレベルの式/文を削除。同じプロジェクト内に 複数のエントリポイント が残らないようにします。
書き換え前(トップレベル)→ 書き換え後(従来型)の例
書き換え前
using System.Net.Http;
using var http = new HttpClient();
var text = await http.GetStringAsync("[https://example.org](https://example.org)");
Console.WriteLine(text);
書き換え後
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class Program
{
public static async Task Main(string[] args)
{
using var http = new HttpClient();
var text = await http.GetStringAsync("[https://example.org](https://example.org)");
Console.WriteLine(text);
}
}
- トップレベルで
awaitを使っていた場合、Mainはasync Taskかasync Task<int>にします。 - トップレベルの暗黙変数
argsを使っていた場合は、Main(string[] args)で受け取って同等に扱います。 Programクラス名やMainのアクセス修飾子は任意ですが、一般にはpublic class Program+staticMainを推奨。
よくあるエラーと対処
| エラーメッセージの例 | 原因 | 対処 |
|---|---|---|
| Program has more than one entry point. | トップレベルステートメントと Main が同居。 | トップレベルの式/文を削除するか、Main を削除してどちらかに統一。 |
| Program does not contain a static ‘Main’ method… | トップレベルを削除したが Main を追加していない、またはシグネチャが不正。 | static void Main(string[] args) など正しいシグネチャを追加。 |
| Top-level statements must precede namespace and type declarations. | ファイル内で型宣言の後ろにトップレベル文が残存。 | トップレベル文を削除するか、Main に移動。 |
| Top-level statements are not allowed in this context. | クラスライブラリでトップレベル文を書いた。 | トップレベル文を撤去。ライブラリにエントリポイントは不要。 |
ASP.NET Core(Minimal API/新ホスティング)を昔の形に寄せる
ASP.NET Core では .NET 6 以降、Program.cs がトップレベル構文+新ホスティングモデル(WebApplication)で生成されます。これを明示的なエントリポイントに戻す最短例は次のとおりです。
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// サービス登録
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// ミドルウェア
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// ルーティング(Minimal API も従来 Controller もどちらでも可)
app.MapGet("/", () => "Hello API");
app.Run();
}
}
つまり、最小 API を使う/使わないことと、トップレベル構文を使う/使わないことは直交します。Minimal API を採用しつつも、明示的な Main による制御は問題なく可能です。既存の Startup クラス パターンを踏襲したい場合も、Program.Main から CreateHostBuilder を呼び出す形に組み替えられます。
ImplicitUsings とトップレベルの違いを正しく理解する
ImplicitUsings は“自動 using 追加”の MSBuild 設定です。トップレベルとは機能の層が異なります。
| 項目 | トップレベルステートメント | ImplicitUsings |
|---|---|---|
| 役割 | エントリポイント記述の簡略化(構文) | 共通名前空間の自動インポート(ビルド設定) |
| 設定場所 | なし(使うか書かないか) | .csproj の <ImplicitUsings> |
| 既定値 | テンプレートに依存 | 多くの .NET 6+ テンプレートで enable |
| 関連するキーワード | await のトップレベル利用、暗黙 args | global using、SDK が注入する名前空間 |
ImplicitUsings を無効にするには以下のようにします(トップレベルの有無には無関係)。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
個別の自動 using を微調整したい場合は、自前で global using を追加/削除(置き換え)してプロジェクトの“見え方”を制御します。
言語バージョンで封じる(組織ポリシーのための最終手段)
“そもそもこの構文が使えないようにしたい”というガバナンス要件があるなら、言語バージョンを C# 8 以下に固定します。ただし当然ながら C# 9 以降の全ての新機能が使えなくなるため、トレードオフは重いことを理解して使いましょう。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion> <!-- C# 8 に固定 -->
</PropertyGroup>
</Project>
マルチターゲットのプロジェクトでは、対象フレームワーク固有の PropertyGroup を用意して意図しないターゲットに影響しないよう注意します。
移行時の落とし穴と対処集(FAQ)
Q. トップレベルで宣言していたローカル関数や using var の寿命はどうなる?
トップレベルに書いていたものは Main の中にそのまま移し、Main のスコープが寿命になります。using var は Main 終了時に破棄されます。
Q. ログ初期化や DI コンテナ構築など、前処理を整理したい。
Main を分割し、BuildServices() や RunAsync() に切り出すと移行しやすく、テストもしやすくなります。
public class Program
{
public static async Task Main(string[] args)
{
using var provider = BuildServices();
await RunAsync(provider, args);
}
private static ServiceProvider BuildServices()
{
var services = new ServiceCollection();
// services.Add... など
return services.BuildServiceProvider();
}
private static Task RunAsync(IServiceProvider provider, string[] args)
{
// 実処理
return Task.CompletedTask;
}
}
Q. Windows 向け(WinForms/WPF)は?
WinForms/WPF のテンプレートはもともと Program クラス+ Main を採用しています。トップレベル構文に依存していないため、本記事の対応は不要です。
Q. テストプロジェクト(xUnit/NUnit/MSTest)への影響は?
テストプロジェクトは 実行可能 EXE ではなくテストランナーの対象ライブラリとして扱われます。トップレベル文を含める意味はないため、基本的に従来どおりで問題ありません。
Q. Main の戻り値は void と int どちらが良い?
プロセスの終了コードを CI/CD で活用するなら int(または Task<int>)が有用です。エラー時に Environment.ExitCode = 1; とする運用もありますが、戻り値で返す方が明示的です。
チーム運用のベストプラクティス
1) テンプレートとスキャフォールドの標準化
- CLI からの新規作成は
--use-program-mainを徹底。 - 既存テンプレートを社内 Git に“雛形”としてコミットしておくと、迷いが減ります。
2) .editorconfig でスタイルを固定
Visual Studio/Roslyn には「トップレベルに変換」コードアクション(IDE の提案)があります。変換を誘導しないために .editorconfig で好みを明示しましょう。
# C# コードスタイル
[* .cs]
csharp_style_prefer_top_level_statements = false:suggestion
dotnet_style_qualification_for_field = true:suggestion
dotnet_style_qualification_for_property = true:suggestion
上記は変換をエラーにするものではありませんが、IDE の提案を抑制し“チームの流儀”を守りやすくします。
3) Analyzer/ビルドルールで入口の形をチェック
自作の Roslyn Analyzer で Program.cs の形を検査し、CI で落とす運用も可能です。簡易には、リポジトリのテンプレート差分チェック(Program.cs のスニペット一致)でも一定の抑止効果があります。
ケース別リファレンス
| ケース | 推奨アクション | 補足 |
|---|---|---|
| コンソールアプリを新規作成 | --use-program-main または VS のチェック | 最初から従来型の Program.cs が生成される |
| 既存のトップレベルを戻したい | コードを Main に移動し、上部の式/文を削除 | 暗黙の args と await に注意 |
| ASP.NET Core(Minimal API) | Program.Main を明示して WebApplication を構成 | Minimal API 採否は別問題。両立可能 |
| ImplicitUsings を切りたい | <ImplicitUsings>disable</ImplicitUsings> | トップレベル構文の採否とは無関係 |
| 組織でトップレベル自体を禁止 | <LangVersion>8.0</LangVersion> に固定 | 新機能を失うトレードオフに注意 |
サンプル:従来型テンプレートの最小構成(.csproj+Program.cs)
プロジェクトファイル(.csproj)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings> <!-- 任意 -->
<Nullable>enable</Nullable> <!-- 任意 -->
<Deterministic>true</Deterministic>
</PropertyGroup>
</Project>
Program.cs(従来型)
using System;
using System.Threading.Tasks;
public class Program
{
public static async Task Main(string[] args)
{
try
{
await RunAsync(args);
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return 1;
}
}
private static Task RunAsync(string[] args)
{
Console.WriteLine($"args length = {args.Length}");
return Task.CompletedTask;
}
}
メリット・デメリットの現実解
| 方式 | メリット | デメリット |
|---|---|---|
| トップレベルステートメント | 短く読みやすい/学習用に最適/サンプルがすっきり | 既存資料との表記差で初学者が混乱/入口の責務が曖昧になりがち |
従来の Main() 方式 | エントリポイントが明示/責務分離しやすい/大規模でも馴染む | ボイラープレートが増える/サンプルでは冗長に見える |
実務で迷わないためのチェックリスト
- 新規作成:テンプレートでトップレベルを避ける(VS のチェック or
--use-program-main)。 - 既存移行:トップレベルの式/文を
Mainに移し、ファイル先頭から撤去。awaitとargsを忘れない。 - 設定の切り分け:ImplicitUsings/Nullable は別軸。要件に応じて
.csprojで管理。 - ガバナンス:必要なら
LangVersion固定、または .editorconfig で変換提案を抑制。 - チーム標準:雛形プロジェクトを共有し、PR で入口構造の逸脱をレビュー。
まとめ
トップレベルステートメントは「無効化する」のではなく、使わなければ良いだけの機能です。新規プロジェクトではテンプレート選択を誤らない、既存プロジェクトではトップレベルの式/文を Main に移して削除する——この 2 点を押さえれば迷いは消えます。ImplicitUsings は別機能なので混同せず、.csproj と .editorconfig でチームの方針を固定しましょう。どうしても構文自体を封じたい状況では LangVersion 固定も選択肢ですが、機能損失のコストをよく吟味してください。“最小の変更で最大の明瞭さ”を合言葉に、あなたのプロジェクトに最適な入口設計を選びましょう。

コメント