练习 - 创建并引发异常

已完成

开发人员通常需要从方法中创建和引发异常,然后在可以处理这些异常的调用堆栈中进一步捕获这些异常。 异常处理有助于确保应用程序的稳定性。

在本练习中,你将从示例应用程序开始,该示例应用程序在调用的方法中包含潜在的错误条件。 更新后的方法在检测到问题时会出现 throw 异常。 异常将在调用方法的代码块中 catch 处理。 结果是一个应用程序,可提供更好的用户体验。

创建新的代码项目

第一步是创建可在本模块中使用的代码项目。

  1. 打开“Visual Studio Code”的新实例。

  2. 在“文件”菜单中,选择“打开文件夹”

  3. “打开文件夹 ”对话框中,导航到 Windows 桌面 文件夹。

  4. “打开文件夹 ”对话框中,选择“ 新建文件夹”。

  5. 将新文件夹 命名为 ThrowExceptions101,然后选择“ 选择文件夹”。

  6. 在“终端”菜单中,选择“新终端”。

    你将使用 .NET CLI 命令创建新的控制台应用。

  7. 在终端面板命令提示符处,输入以下命令:

    dotnet new console
    
  8. 关闭“终端”面板。

查看示例应用程序

使用以下步骤加载和查看示例应用程序。

  1. 打开 Program.cs 文件。

  2. 在“视图”菜单中,选择“命令面板”

  3. 在命令提示符下,输入 .net:g,然后选择 .NET:生成构建和调试的资产

  4. 将 Program.cs 文件的内容替换为以下代码:

    // Prompt the user for the lower and upper bounds
    Console.Write("Enter the lower bound: ");
    int lowerBound = int.Parse(Console.ReadLine());
    
    Console.Write("Enter the upper bound: ");
    int upperBound = int.Parse(Console.ReadLine());
    
    decimal averageValue = 0;
    
    // Calculate the sum of the even numbers between the bounds
    averageValue = AverageOfEvenNumbers(lowerBound, upperBound);
    
    // Display the value returned by AverageOfEvenNumbers in the console
    Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}.");
    
    // Wait for user input
    Console.ReadLine();
    
    static decimal AverageOfEvenNumbers(int lowerBound, int upperBound)
    {
        int sum = 0;
        int count = 0;
        decimal average = 0;
    
        for (int i = lowerBound; i <= upperBound; i++)
        {
            if (i % 2 == 0)
            {
                sum += i;
                count++;
            }
        }
    
        average = (decimal)sum / count;
    
        return average;
    }
    
  5. 花点时间查看代码。

    请注意,应用程序执行以下任务:

    1. 顶级语句使用Console.ReadLine()语句获取其值lowerBoundupperBound

    2. 调用方法时AverageOfEvenNumbers,顶级语句传递lowerBoundupperBound作为参数。

    3. AverageOfEvenNumbers 方法执行以下任务:

      1. 声明计算中使用的局部变量。
      2. for使用循环对介于和之间的lowerBound偶数求和 upperBound。 总和存储在 sum.
      3. 计算总和中包含的数字数。 计数存储在 count.
      4. 将求和数字的平均值存储在名为 <a0/a0> 的变量中。 返回的值 average
    4. 顶级语句将输出由控制台返回 AverageOfEvenNumbers 的值,然后暂停执行。

配置调试环境

示例应用程序从控制台读取用户输入。 调试控制台面板不支持从控制台读取输入。 需要先更新 launch.json 文件,然后才能在调试器中运行此应用程序。

  1. 使用“资源管理器”视图打开 launch.json 文件。

  2. 在 launch.json 文件中,更新 console 属性,如下所示:

    // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
    "console":"integratedTerminal",
    

    console 属性的默认值为 internalConsole,与“调试控制台”面板一致。 遗憾的是,“调试控制台”面板不支持控制台输入。 integratedTerminal 设置支持控制台输入和输出,与“终端”面板一致。

  3. 将更改保存到 launch.json 文件,然后关闭该文件。

  4. 在 Visual Studio Code 的“运行”菜单上,选择“开始调试”。

  5. 切换到终端面板。

  6. 在“下限”提示符下,输入 3

  7. 在“上限”提示符下,输入 11

  8. 请注意,应用程序会显示以下消息,然后暂停:

    The average of even numbers between 3 and 11 is 7.
    
  9. 若要退出应用程序,请按 Enter。

在 AverageOfEvenNumbers 方法中引发异常

该方法 AverageOfEvenNumbers 需要大于下限的上限。 DivideByZero如果下限大于或等于上限,则会发生错误。

当下限大于或等于上限时,需要更新 AverageOfEvenNumbers 方法以引发异常。

  1. 花点时间考虑如何解决问题。

    一个选项是包装代码块内的try计算average,并在catchDivideByZero发生异常时进行。 可以重新引发异常,然后在调用代码中处理该异常。

    另一个选项是在开始计算之前评估输入参数。 如果 lowerBound 大于或等于 upperBound,可以引发异常。

    在开始计算之前评估参数并引发异常是更好的选择。

  2. 考虑要引发的异常类型。

    有两种与问题相符的异常类型:

    • ArgumentOutOfRangeException ArgumentOutOfRangeException- 仅当参数的值超出被调用方法定义的允许值范围之外时,才应引发异常类型。 虽然 AverageOfEvenNumbers 没有显式定义允许的范围 lowerBound ,或者 upperBound,其 lowerBound 值确实意味着允许的范围 upperBound
    • InvalidOperationException InvalidOperationException:仅当方法的作条件不支持成功完成特定方法调用时,才应引发异常类型。 在这种情况下,作条件由方法的输入参数建立。

    如果有两种或更多个要从中选择的异常类型,请选择更贴近问题的异常类型。 在这种情况下,这两种异常类型均与问题保持一致。

    如果两个或多个与问题保持一致的异常类型,请选择范围最窄的异常类型。 ArgumentOutOfRangeException异常类型的范围限定为传递给方法的参数。 异常 InvalidOperationException 类型的范围限定为方法的作条件。 在这种情况下, ArgumentOutOfRangeException 异常类型的范围比 InvalidOperationException 异常类型更窄。

    该方法 AverageOfEvenNumbers 应引发异常 ArgumentOutOfRangeException

  3. 在方法顶部 AverageOfEvenNumbers ,若要检测上限问题,请按如下所示更新代码:

    if (lowerBound >= upperBound)
    {
    
    }
    
    int sum = 0;    
    
  4. 若要创建并引发 ArgumentOutOfRangeException 异常,请按如下所示更新 if 代码块:

    if (lowerBound >= upperBound)
    {
        throw new ArgumentOutOfRangeException("upperBound", "ArgumentOutOfRangeException: upper bound must be greater than lower bound.");
    }
    

    此代码行使用导致异常和指定的错误消息的输入参数的名称初始化类的新实例 ArgumentOutOfRangeException

捕获调用代码中的异常

如果可能,应在可以处理异常的调用堆栈级别捕获异常。 在此示例应用程序中,方法的参数 AverageOfEvenNumbers 可以在调用方法(顶级语句)中管理。

  1. 向上滚动到顶级语句。

  2. 若要将方法调用和Console.WriteLine语句括在AverageOfEvenNumbers代码块内try,请按如下所示更新代码:

    try
    {
        // Calculate the sum of the even numbers between the bounds
        averageValue = AverageOfEvenNumbers(lowerBound, upperBound);
    
        // Display the result to the user
        Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}.");
    }
    
  3. 若要创建关联的 catch 子句,请输入以下代码:

    catch(ArgumentOutOfRangeException ex)
    {
    
    }
    
  4. 花点时间考虑如何处理异常。

    若要处理此异常,代码需要执行以下作:

    • 向用户解释问题。
    • 获取新值 upperBound
    • 使用新upperBound调用 AverageOfEvenNumbers
    • catch如果提供的新upperBound项仍然小于或等于lowerBound,请继续执行异常。

    继续执行 catch 异常需要循环。 由于至少要调用 AverageOfEvenNumbers 一次方法,因此应使用循环 do

  5. 若要将和catch块括try在循环do中,请按如下所示更新代码:

    do
    {
        try
        {
            // Calculate the sum of the even numbers between the bounds
            averageValue = AverageOfEvenNumbers(lowerBound, upperBound);
    
            // Display the result to the user
            Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}.");
        }
        catch (ArgumentOutOfRangeException ex)
        {
    
        }
    }
    

    while定义循环的do退出条件需要表达式。 在定义代码块的内容 do 之前,很难指定条件。 完成 catch 代码块将帮助你定义 while 所需的表达式。

  6. 若要向用户解释问题并获取新的 upperBound代码块,请按如下所示更新 catch 代码块:

    catch (ArgumentOutOfRangeException ex)
    {
        Console.WriteLine("An error has occurred.");
        Console.WriteLine(ex.Message);
        Console.WriteLine($"The upper bound must be greater than {lowerBound}");
        Console.Write($"Enter a new upper bound: ");
        upperBound = int.Parse(Console.ReadLine());
    }
    

    更新 catch 的代码块描述问题,并要求用户输入新的上限。 但是,如果用户没有输入有效的上限值,该怎么办? 如果用户需要退出循环而不是输入值,该怎么办?

  7. 若要为用户提供退出循环的选项,而不是输入新的上限,请按如下所示更新 catch 代码块:

    catch (ArgumentOutOfRangeException ex)
    {
        Console.WriteLine("An error has occurred.");
        Console.WriteLine(ex.Message);
        Console.WriteLine($"The upper bound must be greater than {lowerBound}");
        Console.Write($"Enter a new upper bound (or enter Exit to quit): ");
        string? userResponse = Console.ReadLine();
        if (userResponse.ToLower().Contains("exit"))
        {
    
        }
        else
        {
            upperBound = int.Parse(userResponse);
        }
    }
    

    更新 catch 的代码块包括两个路径:“exit”路径和“新的上限”路径。

  8. 花一分钟时间考虑 while 循环所需的 do 表达式。

    如果用户在提示符下输入“退出”,代码应退出循环。 如果用户输入新的上限,循环应继续。 while可以使用计算布尔值的表达式。 例如:

    while (exit == false);
    

    建议的 while 表达式将建立以下行为:

    • 只要布尔exit值等于 false,循环do就会继续循环访问。
    • 一旦布尔exit值等于 true,循环do就会停止迭代。
  9. 若要实例化一个名为 exit布尔变量,并用于 exit 设置循环的 do 退出条件,请按如下所示更新代码:

    bool exit = false;
    
    do
    {
        try
        {
            // Calculate the sum of the even numbers between the bounds
            averageValue = AverageOfEvenNumbers(lowerBound, upperBound);
    
            // Display the result to the user
            Console.WriteLine($"The average of even numbers between {lowerBound} and {upperBound} is {averageValue}.");
    
            exit = true;
        }
        catch (ArgumentOutOfRangeException ex)
        {
            Console.WriteLine("An error has occurred.");
            Console.WriteLine(ex.Message);
            Console.WriteLine($"The upper bound must be greater than {lowerBound}");
            Console.Write($"Enter a new upper bound (or enter Exit to quit): ");
            string? userResponse = Console.ReadLine();
            if (userResponse.ToLower().Contains("exit"))
            {
                exit = true;
            }
            else
            {
                exit = false;
                upperBound = int.Parse(userResponse);
            }
        }    
    } while (exit == false);
    
  10. 保存更新的代码。

  11. 在“运行”菜单上,选择“开始调试”。

  12. 切换到终端面板。

  13. 在“下限”提示符下,输入 3

  14. 在“上限”提示符下,输入 3

  15. 请注意,终端面板中显示以下输出:

    Enter the lower bound: 3
    Enter the upper bound: 3
    An error has occurred.
    ArgumentOutOfRangeException: upper bound must be greater than lower bound. (Parameter 'upperBound')
    The upper bound must be greater than 3
    Enter a new upper bound (or enter Exit to quit):
    
  16. 在提示输入新的上限时,输入 11

  17. 请注意,终端面板中显示以下输出:

    Enter the lower bound: 3
    Enter the upper bound: 3
    An error has occurred.
    ArgumentOutOfRangeException: upper bound must be greater than lower bound. (Parameter 'upperBound')
    The upper bound must be greater than 3
    Enter a new upper bound (or enter Exit to quit): 11
    The average of even numbers between 3 and 11 is 7.
    
  18. 若要退出应用程序,请按 Enter。

祝贺! 已成功引发、捕获和处理异常。

回顾

在本单元中,应谨记以下几个重要事项:

  • 确保调试环境配置为支持应用程序要求。
  • 检测到问题或条件时,方法代码应引发异常。
  • 异常应在可解析的调用堆栈中的级别捕获。