次の方法で共有


最上位レベルのステートメント

手記

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオンの課題: https://github.com/dotnet/csharplang/issues/2765

概要

一連のステートメントが、compilation_unit (ソース ファイル) の namespace_member_declaration の直前に出現することを許可します。

セマンティクスとしては、このような一連のステートメントが存在する場合、型宣言は次のようになります (実際のメソッド名を除く)。

partial class Program
{
    static async Task Main(string[] args)
    {
        // statements
    }
}

https://github.com/dotnet/csharplang/issues/3117も参照してください。

モチベーション

明示的な Main メソッドが必要であるため、プログラムの最も単純な部分も囲む定型句は一定量あります。 これは、言語学習とプログラムの明確さの邪魔になるようです。 そのため、この機能の主な目的は、学習者とコードの明確さのために、不要な定型句なしで C# プログラムを許可することです。

詳細な設計

構文

唯一の追加構文は、コンパイル単位で namespace_member_declaration の直前に一連のステートメントを許可することです。

compilation_unit
    : extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
    ;

compilation_unit には、ステートメントが 1 つだけ許可されます。

例:

if (args.Length == 0
    || !int.TryParse(args[0], out int n)
    || n < 0) return;
Console.WriteLine(Fib(n).curr);

(int curr, int prev) Fib(int i)
{
    if (i == 0) return (1, 0);
    var (curr, prev) = Fib(i - 1);
    return (curr + prev, curr);
}

意味論

最上位レベルのステートメントがプログラムのコンパイル単位に存在する場合、意味は、グローバル名前空間の Program クラスの Main メソッドのブロック本体で結合されたかのように、次のようになります。

partial class Program
{
    static async Task Main(string[] args)
    {
        // statements
    }
}

型は "Program" という名前なので、ソース コードから名前で参照できます。 これは部分型であるため、ソース コードの "Program" という名前の型も partial として宣言する必要があります。
ただし、メソッド名 "Main" は例示目的でのみ使用されます。コンパイラによって使用される実際の名前は実装に依存し、ソース コードから名前でメソッドを参照することはできません。

このメソッドは、プログラムのエントリ ポイントとして指定されます。 規則によってエントリ ポイント候補と見なすことができる明示的に宣言されたメソッドは無視されます。 その場合、警告が報告されます。 最上位レベルのステートメントがある場合、コンパイラ スイッチ -main:<type> 指定するとエラーになります。

エントリ ポイント メソッドには、常に 1 つの仮パラメーター string[] argsがあります。 実行環境は、アプリケーションの起動時に指定されたコマンド ライン引数を含む string[] 引数を作成して渡します。 string[] 引数は null になることはありませんが、コマンド ライン引数が指定されていない場合は長さが 0 になる可能性があります。 'args' パラメーターは、最上位レベルのステートメント内のスコープ内にあり、スコープ外にありません。 通常の名前の競合/シャドウイングのルールが適用されます。

非同期操作は、通常の非同期エントリ ポイント メソッド内のステートメントで許可される程度まで、最上位のステートメントで許可されます。 ただし、これらは必須ではありません。await 式やその他の非同期操作を省略しても、警告は生成されません。

生成されたエントリ ポイント メソッドのシグネチャは、最上位レベルのステートメントで使用される操作に基づいて次のように決定されます。

Async-operations\Return-with-expression 存在する 欠席
存在する static Task<int> Main(string[] args) static Task Main(string[] args)
欠席 static int Main(string[] args) static void Main(string[] args)

上記の例では、次の $Main メソッド宣言が生成されます。

partial class Program
{
    static void $Main(string[] args)
    {
        if (args.Length == 0
            || !int.TryParse(args[0], out int n)
            || n < 0) return;
        Console.WriteLine(Fib(n).curr);
        
        (int curr, int prev) Fib(int i)
        {
            if (i == 0) return (1, 0);
            var (curr, prev) = Fib(i - 1);
            return (curr + prev, curr);
        }
    }
}

同時に、次のような例があります。

await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");

次の結果が得られます。

partial class Program
{
    static async Task $Main(string[] args)
    {
        await System.Threading.Tasks.Task.Delay(1000);
        System.Console.WriteLine("Hi!");
    }
}

次のような例を次に示します。

await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;

次の結果が得られます。

partial class Program
{
    static async Task<int> $Main(string[] args)
    {
        await System.Threading.Tasks.Task.Delay(1000);
        System.Console.WriteLine("Hi!");
        return 0;
    }
}

次のような例を次に示します。

System.Console.WriteLine("Hi!");
return 2;

次の結果が得られます。

partial class Program
{
    static int $Main(string[] args)
    {
        System.Console.WriteLine("Hi!");
        return 2;
    }
}

最上位レベルのローカル変数とローカル関数のスコープ

最上位レベルのローカル変数と関数は、生成されたエントリ ポイント メソッドに "ラップ" されますが、すべてのコンパイル ユニットでプログラム全体のスコープ内に存在する必要があります。 単純な名前の評価を目的として、グローバル名前空間に到達したら、次の操作を行います。

  • 最初に、生成されたエントリポイントメソッド内で名前が評価される試みが行われ、試みが失敗した場合にのみ行われます。
  • グローバル名前空間宣言内の "標準" 評価が実行されます。

これにより、グローバル名前空間内で宣言された名前空間と型の名前シャドウや、インポートされた名前のシャドウが発生する可能性があります。

単純な名前の評価が最上位レベルのステートメントの外部で行われ、評価によって最上位のローカル変数または関数が生成された場合、エラーが発生します。

この方法で、"最上位レベルの関数" (https://github.com/dotnet/csharplang/issues/3117のシナリオ 2) に対する今後の対処能力を保護し、サポートされていると誤って信じているユーザーに有用な診断を提供できます。