トップレベルステートメント

.NET5.0/C#9.0より、トップレベルステートメントというC#の新しい記法がサポートされています。
Visual Studio2022では、プロジェクトの作成時に設定を変更しなければトップレベルステートメントの機能を使用したソースコードがテンプレートとして自動生成されます。
(今後のバージョンで初期設定が変わる可能性はあります)

プロジェクト作成時に「最上位レベルのステートメントを使用しない」というチェックボックスをオンにすると、従来通りのソースコードが自動生成されます。
最上位レベルでのステートメントを使用しないチェックボックスのオン


namespace ConsoleApplication1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

ここではトップレベルステートメントを有効にした場合(チェックボックスをオフにした場合)についてを説明します。

Mainメソッド

トップレベルステートメントを有効にしてプロジェクト作成を作成すると、以下のようなソースコードが自動生成されます。


// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

一行目はただのコメントなので、実質的にConsole.WriteLineメソッド一行だけのコードです。
従来のC#コードとは構成が大きく異なりますが、これだけでコンパイルと実行が可能です。

トップレベルステートメントは、クラスなどの内部ではないコードの最上位の場所にいきなり文(ステートメント)を書くことができる記法です。
上記コードは、コンパイル時に以下と同等のコードに置き換えられます。


class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

あくまで同等の動作をするコードであって、「Program」や「Main」などの名前はそのままではない可能性があります。
どのような名前で生成されるかは規格では規格では決められいないようですが、今後のバージョンで使用される可能性があるため、「Programクラス」を定義するのは避けた方が無難です。

トップレベルステートメントが有効になるのはプロジェクト全体でひとつのソースコードだけです。
そのソースコードにMainメソッド相当の処理が展開されます。

ソースコード上では一行だけですが、初期設定ではいくつかのusingディレクティブが自動的に追加されます。
詳細は暗黙的なusingディレクティブの項を参照してください。

コマンドライン引数

上記のコードでも示していますが、トップレベルステートメントにはargsという名前のstring型配列の引数が自動的に定義されます。
これはコマンドライン引数です。
詳しくは当該ページを参照してください。


Console.WriteLine(
    "{0}",
    args.Length == 0 ? "引数なし" : args[0]);

return文と戻り値

トップレベルステートメントにはreturn文を書くこともできます。
これはMainメソッドのreturn文なので戻り値はアプリケーションの終了コードです。
int型、もしくは指定しないことができます。

int型を指定した場合、Mainメソッドの戻り値はint型になります。


Console.WriteLine("Hello World!");
return 0;

//↓

static int Main(string[] args)
{
    Console.WriteLine("Hello World!");
    return 0;
}

何も指定しない場合はMainメソッドの戻り値はvoid型になります。


Console.WriteLine("Hello World!");
return;

//↓

static void Main(string[] args)
{
    Console.WriteLine("Hello World!");
    return 0;
}

非同期メソッドの使用

トップレベルステートメントでawaitを使用することもできます。

awaitを使用した場合、Mainメソッドの戻り値の型はTask型、またはTask<int>型になります。

Mainメソッド以外の定義

ユーザー定義型(クラス、列挙型等)

トップレベルステートメントを記述するソースコードには、通常のソースコードと同じようにusingディレクティブ(usingエイリアス)や名前空間クラス構造体列挙型などを書くことができます。
ただしトップレベルステートメントよりも手前に書けるのはusingディレクティブおよびエイリアスのみで、クラスや構造体、列挙型の定義(ユーザー定義型の定義)はトップレベルステートメントの後ろに記述する必要があります。


using System; //usingディレクティブは先頭に書ける

//ここにクラスや列挙型の定義を書くとエラー
//class C { }
//enum E { }

//トップレベルステートメント
Console.WriteLine("Hello World!");

//トップレベルステートメントの後ろならOK
class C { }
enum E { }

//クラス等の後ろにはもうトップレベルステートメントは書けない
//Console.WriteLine("Hello World!");

ユーザー定義メソッド

メソッドの位置は自由で、トップレベルステートメントの前後どちらにも書くことができます。


void M1(string s)
{
    Console.Write(s);
}

M1("Hello ");
M2("World!");

void M2(string s)
{
    Console.WriteLine(s);
}    

トップレベルに書かれたメソッドは、コンパイル時にMainメソッドのローカル関数の扱いになります。
つまりトップレベルステートメント以外からは実行できません。
(変数も同じ)

Mainメソッド内のローカル関数になるという都合上、メソッドのオーバーロードは使用できません。


void M(int n)
{
    Console.WriteLine(n);
}

//同じ名前のローカル関数の再定義はできない
//コンパイルエラー
void M(string s)
{
    Console.WriteLine(a);
}

通常のローカル関数は、他のメソッドからは「見えない」ので実行できませんが、トップレベルステートメントに記述したメソッドは、他のメソッドからは「見える」けれど実行できない、特殊なローカル関数となります。