次の方法で共有


チュートリアル: 学習しながらコードをビルドするために最上位レベルのステートメントを使用してアイデアを探索する

このチュートリアルでは、次の作業を行う方法について説明します。

  • 最上位レベルのステートメントの使用を制御するルールについて学習する。
  • 最上位レベルのステートメントを使用してアルゴリズムを探索する。
  • 探索を再利用可能なコンポーネントにリファクタリングする。

必須コンポーネント

.NET 6 以降が実行されるように、お使いのコンピューターをセットアップする必要があります。 C# コンパイラは、Visual Studio 2022 以降または .NET SDK 以降で使用できます。

このチュートリアルでは、C# と .NET (Visual Studio または .NET CLI のいずれかを含む) に精通していることを前提としています。

実際に使ってみる

トップレベルのステートメントを使用すると、クラス内の静的メソッドにプログラムのエントリ ポイントを置く必要がある余計な儀式的な手順を回避できます。 新しいコンソール アプリケーションの一般的な開始点は、次のコードのようになります。

using System;

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

上記のコードは、dotnet new console コマンドを実行し、新しいコンソール アプリケーションを作成した結果です。 これらの 11 行には、実行可能コードが 1 行しか含まれていません。 そのプログラムは、新しい最上位レベルのステートメント機能を使用して簡素化できます。 これにより、このプログラム内の 2 つを除くすべての行を削除できます。

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

重要

.NET 6 の C# テンプレートでは "最上位レベルのステートメント" が使用されます。 .NET 6 に既にアップグレードしている場合、お使いのアプリケーションはこの記事のコードに一致しないかもしれません。 詳細については、「新しい C# テンプレートで、最上位レベルのステートメントが生成される」の記事を参照してください。

.NET 6 SDK により、次の SDK を使用するプロジェクトに対して暗黙的なglobal usingディレクティブ セットも追加されます。

  • Microsoft.NET.Sdk
  • Microsoft.NET.Sdk.Web
  • Microsoft.NET.Sdk.Worker

これらの暗黙的な global using ディレクティブには、プロジェクトの種類に応じて最も一般的な名前空間が含まれます。

詳細については、「暗黙的な using ディレクティブ」の記事を参照してください

この機能により、新しいアイデアの探索が簡単になります。 最上位レベルのステートメントをスクリプト作成シナリオで、あるいは探索するために使用できます。 基本的な作業が完了したら、コードのリファクタリングを開始し、ビルドした再利用可能なコンポーネントに対してメソッド、クラス、またはその他のアセンブリを作成できます。 最上位レベルのステートメントを使用すれば、迅速な実験と初心者向けチュートリアルが可能になります。 また、実験から完全なプログラムへのスムーズなパスが得られます。

最上位レベルのステートメントは、ファイルに示される順序で実行されます。 最上位レベルのステートメントは、アプリケーション内の 1 つのソース ファイルのみで使用できます。 複数のファイルで使用すると、コンパイラでエラーが発生します。

マジック .NET 回答マシンをビルドする

このチュートリアルでは、"はい" か "いいえ" の質問にランダムな回答で答えるコンソール アプリケーションをビルドします。 機能の構築は段階的に行います。 一般的なプログラムの構造で必要とされる形式ばられた手続きではなく、自分のタスクに集中できます。 その後、機能に問題がなければ、必要に応じてアプリケーションをリファクタリングすることができます。

まず、質問をコンソールに書き戻すことをお勧めします。 まず、次のコードを記述します。

Console.WriteLine(args);

args 変数は宣言しません。 最上位レベルのステートメントを含む単一のソース ファイルの場合、コンパイラでコマンドライン引数を意味する args が認識されます。 すべての C# プログラムの場合と同じように、引数の型は string[] となります。

次の dotnet run コマンドを実行して、コードをテストすることができます。

dotnet run -- Should I use top level statements in all my programs?

コマンド ラインの -- の後の引数は、プログラムに渡されます。 args 変数の型がコンソールに出力されていることを確認できます。

System.String[]

コンソールに質問を書き込むには、引数を列挙し、それらをスペースで区切る必要があります。 WriteLine 呼び出しを次のコードに置き換えます。

Console.WriteLine();
foreach(var s in args)
{
    Console.Write(s);
    Console.Write(' ');
}
Console.WriteLine();

これで、プログラムを実行すると、引数の文字列として質問が正しく表示されるようになります。

ランダムな回答で答える

質問を問い返した後、ランダムな回答を生成するコードを追加できます。 まず、考えられる答えの配列を追加します。

string[] answers =
[
    "It is certain.",       "Reply hazy, try again.",     "Don’t count on it.",
    "It is decidedly so.",  "Ask again later.",           "My reply is no.",
    "Without a doubt.",     "Better not tell you now.",   "My sources say no.",
    "Yes – definitely.",    "Cannot predict now.",        "Outlook not so good.",
    "You may rely on it.",  "Concentrate and ask again.", "Very doubtful.",
    "As I see it, yes.",
    "Most likely.",
    "Outlook good.",
    "Yes.",
    "Signs point to yes.",
];

この配列に含まれる答えは、肯定的なものが 10 個、あいまいなものが 5 個、否定的なものが 5 個です。 次に、配列からランダムな回答を生成して表示する以下のコードを追加します。

var index = new Random().Next(answers.Length - 1);
Console.WriteLine(answers[index]);

アプリケーションをもう一度実行して、結果を確認することができます。 次の出力のようになるはずです。

dotnet run -- Should I use top level statements in all my programs?

Should I use top level statements in all my programs?
Better not tell you now.

回答を生成するコードには、最上位レベルのステートメントに変数宣言が含まれています。 コンパイラはその宣言を、コンパイラによって生成された Main メソッドに含めます。 これらの変数宣言はローカル変数であるため、static 修飾子を含めることはできません。

このコードで質問に回答しますが、もう 1 つ機能を追加してみましょう。 たとえば、質問アプリを使用して、答えについての考え方をシミュレートしたいとします。 これは、ASCII アニメーションを少し追加し、作業を一時中断して行うことができます。 質問を問い返す行の後に、次のコードを追加します。

for (int i = 0; i < 20; i++)
{
    Console.Write("| -");
    await Task.Delay(50);
    Console.Write("\b\b\b");
    Console.Write("/ \\");
    await Task.Delay(50);
    Console.Write("\b\b\b");
    Console.Write("- |");
    await Task.Delay(50);
    Console.Write("\b\b\b");
    Console.Write("\\ /");
    await Task.Delay(50);
    Console.Write("\b\b\b");
}
Console.WriteLine();

using ディレクティブをソース ファイルの先頭に追加する必要もあります。

using System.Threading.Tasks;

using ディレクティブは、ファイル内の他のどのステートメントよりも前に配置する必要があります。 それ以外の場合、コンパイラ エラーになります。 プログラムをもう一度実行して、アニメーションを表示することができます。 これにより、エクスペリエンスが向上します。 ご自分の好みに合わせて遅延の長さを試してください。

上のコードにより、スペースで区切られた一連の回転する行が作成されます。 await キーワードを追加すると、async 修飾子を持ち、System.Threading.Tasks.Task を返すメソッドとしてプログラムのエントリ ポイントを生成するようにコンパイラに指示されます。 このプログラムからは値が返されないため、プログラムのエントリ ポイントにより Task が返されます。 プログラムから整数値が返された場合は、最上位レベルのステートメントの末尾に return ステートメントを追加します。 その return ステートメントにより、返す整数値が指定されます。 最上位レベルのステートメントに await 式が含まれている場合は、戻り値の型が System.Threading.Tasks.Task<TResult> になります。

未来に向けたリファクタリング

プログラムのコードは次のようになるはずです。

Console.WriteLine();
foreach(var s in args)
{
    Console.Write(s);
    Console.Write(' ');
}
Console.WriteLine();

for (int i = 0; i < 20; i++)
{
    Console.Write("| -");
    await Task.Delay(50);
    Console.Write("\b\b\b");
    Console.Write("/ \\");
    await Task.Delay(50);
    Console.Write("\b\b\b");
    Console.Write("- |");
    await Task.Delay(50);
    Console.Write("\b\b\b");
    Console.Write("\\ /");
    await Task.Delay(50);
    Console.Write("\b\b\b");
}
Console.WriteLine();

string[] answers =
[
    "It is certain.",       "Reply hazy, try again.",     "Don't count on it.",
    "It is decidedly so.",  "Ask again later.",           "My reply is no.",
    "Without a doubt.",     "Better not tell you now.",   "My sources say no.",
    "Yes – definitely.",    "Cannot predict now.",        "Outlook not so good.",
    "You may rely on it.",  "Concentrate and ask again.", "Very doubtful.",
    "As I see it, yes.",
    "Most likely.",
    "Outlook good.",
    "Yes.",
    "Signs point to yes.",
];

var index = new Random().Next(answers.Length - 1);
Console.WriteLine(answers[index]);

上のコードは適切です。 うまくいっています。 しかし、再利用することはできません。 これでアプリケーションが動作するようになったので、次は再利用可能な部分を取得します。

候補の 1 つとして、待機中のアニメーションを表示するコードがあります。 このスニペットはメソッドになることがあります。

まず、ファイルにローカル関数を作成します。 現在のアニメーションを次のコードに置き換えます。

await ShowConsoleAnimation();

static async Task ShowConsoleAnimation()
{
    for (int i = 0; i < 20; i++)
    {
        Console.Write("| -");
        await Task.Delay(50);
        Console.Write("\b\b\b");
        Console.Write("/ \\");
        await Task.Delay(50);
        Console.Write("\b\b\b");
        Console.Write("- |");
        await Task.Delay(50);
        Console.Write("\b\b\b");
        Console.Write("\\ /");
        await Task.Delay(50);
        Console.Write("\b\b\b");
    }
    Console.WriteLine();
}

前のコードで、main メソッド内にローカル関数が作成されています。 このコードはまだ再利用できません。 そのため、そのコードをクラスに抽出します。 utilities.cs という名前の新しいファイルを作成し、次のコードを追加します。

namespace MyNamespace
{
    public static class Utilities
    {
        public static async Task ShowConsoleAnimation()
        {
            for (int i = 0; i < 20; i++)
            {
                Console.Write("| -");
                await Task.Delay(50);
                Console.Write("\b\b\b");
                Console.Write("/ \\");
                await Task.Delay(50);
                Console.Write("\b\b\b");
                Console.Write("- |");
                await Task.Delay(50);
                Console.Write("\b\b\b");
                Console.Write("\\ /");
                await Task.Delay(50);
                Console.Write("\b\b\b");
            }
            Console.WriteLine();
        }
    }
}

最上位レベルのステートメントが含まれているファイルには、そのファイルの最後の最上位レベルのステートメントの後に、名前空間と型を含めることもできます。 ただし、このチュートリアルでは別のファイルにアニメーション メソッドを配置して、より簡単に再利用できるようにします。

最後に、foreach 配列で定義された一連のアニメーション要素をanimations ループを使用して反復処理することにより、アニメーションコードを整理し、重複を排除できます。
リファクタリング後の完全な ShowConsoleAnimation メソッドは、次のコードのようになります。

public static async Task ShowConsoleAnimation()
{
    string[] animations = ["| -", "/ \\", "- |", "\\ /"];
    for (int i = 0; i < 20; i++)
    {
        foreach (string s in animations)
        {
            Console.Write(s);
            await Task.Delay(50);
            Console.Write("\b\b\b");
        }
    }
    Console.WriteLine();
}

これでアプリケーションが完成し、後で使用するために再利用可能な部分がリファクタリングされました。 メイン プログラムの完成版で示されているように、最上位のステートメントから新しいユーティリティ メソッドを呼び出すことができます。

using MyNamespace;

Console.WriteLine();
foreach(var s in args)
{
    Console.Write(s);
    Console.Write(' ');
}
Console.WriteLine();

await Utilities.ShowConsoleAnimation();

string[] answers =
[
    "It is certain.",       "Reply hazy, try again.",     "Don’t count on it.",
    "It is decidedly so.",  "Ask again later.",           "My reply is no.",
    "Without a doubt.",     "Better not tell you now.",   "My sources say no.",
    "Yes – definitely.",    "Cannot predict now.",        "Outlook not so good.",
    "You may rely on it.",  "Concentrate and ask again.", "Very doubtful.",
    "As I see it, yes.",
    "Most likely.",
    "Outlook good.",
    "Yes.",
    "Signs point to yes.",
];

var index = new Random().Next(answers.Length - 1);
Console.WriteLine(answers[index]);

上の例では、Utilities.ShowConsoleAnimation への呼び出しを追加し、別の using ディレクティブを追加しています。

まとめ

最上位レベルのステートメントを利用すると、新しいアルゴリズムを探索するのに使用するシンプルなプログラムをより簡単に作成できます。 異なるコード スニペットを試すことで、アルゴリズムを試してみることができます。 動作について学習したら、コードをリファクタリングして、より保守しやすいものにすることができます。

最上位レベルのステートメントを使用すると、コンソール アプリに基づくプログラムが簡素化されます。 これらのアプリには、Azure 関数、GitHub アクション、その他の小さなユーティリティが含まれます。 詳細については、「最上位レベルのステートメント (C# プログラミング ガイド)」を参照してください。