教程:在学习过程中,探索使用顶级语句生成代码的想法

在本教程中,你将学习如何:

  • 了解控制顶层语句使用的规则。
  • 使用顶层语句探索算法。
  • 重构对可重用组件的探索。

先决条件

需要将计算机设置为运行 .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 行仅包含一行可执行代码。 可以使用新的顶级语句功能简化该程序。 这使您可以删除该程序中除两行以外的所有行。

// 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 指令的文章

此功能简化了对新想法的探索。 你可以将顶级语句用于脚本编写场景,或用于探索。 一旦您让基础功能正常运行,就可以开始重构代码,为您构建的可重用组件创建方法、类或其他程序集。 顶级语句支持快速试验和初学者教程。 它们还提供从试验到完整程序的平滑路径。

顶级语句按它们在文件中显示的顺序执行。 顶级语句只能在应用程序中的一个源文件中使用。 如果在多个文件中使用这些编译器,编译器将生成错误。

构建神奇的 .NET 应答计算机

在本教程中,我们构建一个控制台应用程序,该应用程序使用随机答案回答“是”或“否”问题。 您逐步构建功能。 你可以专注于你的任务,而不是典型程序结构所需的仪式。 然后,如果对功能感到满意,可以根据需要重构应用程序。

将问题写回控制台是一个良好的起点。 首先可以编写以下代码:

Console.WriteLine(args);

你没有定义 args 变量。 对于包含顶级语句的单个源文件,编译器可识别 args 表示命令行参数。 参数的类型是 string[],与所有 C# 程序一样。

可以通过运行以下 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 修饰符。

此代码回答了问题,但让我们再添加一个功能。 你希望你的问题应用模拟对答案的思考。 为此,可以添加一些 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]);

前面的代码是合理的。 它有效。 但它不能重复使用。 应用程序正常运行后,就可以提取可重用部件了。

一个候选项是显示等待动画的代码。 该代码片段可以成为一种方法:

首先,可以在文件中创建本地函数。 将当前动画替换为以下代码:

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();
}

前面的代码在主方法中创建本地函数。 该代码仍然无法重复使用。 因此,将该代码提取到类中。 创建名为 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 Functions、GitHub 操作和其他小型实用工具。 有关详细信息,请参阅 顶级语句(C# 编程指南)