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

本教程介绍以下操作:

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

先决条件

需要将计算机设置为运行 .NET 6,其中包括 C# 10 编译器。 自 Visual Studio 2022.NET 6 SDK 起,开始提供 C# 10 编译器。

本教程假设你熟悉 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 的项目添加了一组隐式 指令:

  • 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.

此代码回答了这些问题,但我们再添加一个功能。 你想让你的问题应用模拟对答案的思考。 可添加一小段 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();
}

前面的代码在 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 (string s in animations)
{
    Console.Write(s);
    await Task.Delay(50);
    Console.Write("\b\b\b");
}

现在你有了一个完整的应用程序,并且已经重构了可重用部分供以后使用。 可以从顶级语句中调用新的实用工具方法,如下面主程序的最终版本所示:

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# 编程指南)