IRONSOFTWAREHOME

C# 异常处理

C# 异常处理

Tim Corey

59分46秒

异常处理是稳健应用程序开发的一个重要方面。 Tim Corey 的视频"Handling Exceptions in C# - When to catch them, where to catch them, and how to catch them"详细解释了什么是异常、如何处理异常以及在何处处理异常。

本文旨在使用 Tim Corey 的视频解释 C# 中的异常处理。 这是一项强大的功能,允许开发人员管理程序执行过程中出现的错误和特殊情况。 通过使用 try、catch 和 finally 块,C# 提供了一种结构化的方法来处理运行时错误、记录异常并保持程序流程。

简介

Tim 首先解释说,许多开发人员对异常及其处理的看法并不正确。 他强调了理解异常的重要性,以及如何正确处理异常以创建更强大的应用程序。

构建演示控制台应用程序

Tim 在 Visual Studio 2017 中创建了一个控制台应用程序来演示异常处理。 他建议在测试新主题时使用控制台应用程序,因为它们只需要最少的设置,而且易于操作。

using System;

namespace ExceptionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Placeholder for input and output operations
            Console.ReadLine();
        }
    }
}

创建类库

Tim 在解决方案中添加了一个类库,以模拟现实世界中不同方法相互调用的场景。

他删除了默认类,并创建了一个名为 DemoCode 的新类。

public class DemoCode
{
    // Method to retrieve a number based on the provided position
    public int GetNumber(int position)
    {
        int[] numbers = { 1, 4, 7, 2 };
        return numbers[position];
    }

    // Intermediate method calls GetNumber
    public int ParentMethod(int position)
    {
        return GetNumber(position);
    }

    // Top-level method calls ParentMethod
    public int GrandparentMethod(int position)
    {
        return ParentMethod(position);
    }
}

DemoCode 类包含相互调用的方法,最终根据给定的位置从数组中检索一个数字。

模拟异常

Tim 解释说,该应用程序旨在展示失败而非成功。 他通过向 GrandparentMethod 传递无效位置引入了越界异常。

DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");

在位置无效的情况下运行上述代码会导致 IndexOutOfRangeException 异常。 Tim 展示了 Visual Studio 调试器如何突出显示问题并提供有关异常的详细信息。

如何不使用 try-catch.

Tim 解释了开发人员在初次了解 try-catch 块时常犯的一个错误。 他们经常会在预期可能出现异常的地方封装整个代码块,这可能会导致处理不当。

try
{
    int output = 0;
    output = numbers[position];
    return output;
}
catch (Exception ex)
{
    // Avoid returning default values that can mask the problem
    return 0;
}

Tim 强调指出,这种方法存在问题,因为它隐藏了例外情况,并以不正确的假设继续执行。 例如,将 0 作为默认值返回可能并不合适,而且会引起更多问题。

正确的异常处理

Tim 强调,异常提供了有关应用程序中意外状态的重要信息。 如果应用程序继续处于这种状态而不进行适当处理,可能会导致更多错误和数据损坏。

必须妥善处理异常情况,而不是一味吞并异常情况。 这里有一个更好的方法:

try
{
    return numbers[position];
}
catch (Exception ex)
{
    // Log the exception or handle it appropriately
    Console.WriteLine(ex.Message);
    throw; // Re-throw the exception to be handled by a higher-level handler
}

通过重新抛出异常,可以确保问题得到传播,并在必要时在更高层次进行处理。

为用户提供有用的信息

Tim 解释说,虽然可以优雅地处理某些异常而不会导致应用程序崩溃,但重要的是要向用户提供有用的反馈。 例如,显示带有重试操作选项的消息框或通知。

更多有用信息:堆栈跟踪

Tim 演示了如何使用异常对象的 StackTrace 属性来获取异常发生位置的详细信息。 这包括类、方法和行号,这对调试非常重要。

try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
    throw;
}

StackTrace 属性可提供完整的调用堆栈跟踪,帮助开发人员准确定位问题。

正确使用 try-catch.

Tim 解释说,正确处理异常不仅仅是捕获异常,还要知道在哪里放置 try-catch 块。 关键是要将 try-catch 块放在有足够的上下文来适当处理异常的级别。

位置不当示例

将 try-catch 块放在调用堆栈的深处通常无法有效处理异常,因为您缺乏高层操作的上下文。

// Deep level exception handling (not ideal)
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;
}

正确位置示例

将 try-catch 块放在顶层,如用户界面或应用程序的入口点,可以让您根据操作的完整上下文来处理异常。

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

这样,您就可以向用户提供更多信息,并决定应用程序是否可以继续运行或是否应该终止。

堆栈跟踪信息

Tim 强调了堆栈跟踪信息在诊断异常中的重要性。 堆栈跟踪提供了详细的调用历史,显示了异常发生的位置以及导致异常的方法调用链。

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

该输出可提供异常的确切位置和代码路径,从而更容易调试和修复问题。

处理逻辑演示

Tim 演示了如何在适当的级别处理逻辑。 例如,如果一个方法负责打开和关闭数据库连接,它就应该处理异常,以确保资源得到妥善管理。

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        Console.WriteLine("Close database connection");
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Ensure the exception is propagated
    }
}

在此示例中,如果出现异常,数据库连接将无法正确关闭,从而导致潜在的资源泄漏。 通过添加 try-catch 块,可以确保即使出现异常也能关闭连接。

使用最终块

Tim 介绍了 finally 块,它可以确保某些代码无论是否出现异常都能运行。 这对于清理资源(如关闭数据库连接)尤其有用。

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Re-throw the exception to ensure it's handled by a higher-level handler
    }
    finally
    {
        Console.WriteLine("Close database connection");
    }
}

finally 块在 try 和 catch 块之后运行,确保即使出现异常也能关闭连接。

声明

Tim 解释了重新抛出异常以将其传递到调用栈的重要性。 这样上级处理程序就可以适当处理异常。

catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw; // Re-throws the exception to be handled by the calling method
}

用 throw 重新抛出异常; 确保保留完整的堆栈跟踪,为调试提供有价值的上下文。

正确颠簸异常

Tim 演示了异常是如何在调用堆栈中冒泡的。 每个方法都会检查 try-catch 块,并处理异常或将其传递给调用者。

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

在本例中,GrandparentMethod 捕获异常、记录异常并重新抛出异常。 然后,控制台应用程序中的顶级 try-catch 块会处理异常并显示错误消息和堆栈跟踪。

异常处理中的常见错误

Tim 强调了开发人员在处理异常时常犯的几个错误:

1.Using throw ex;:

  • 重写堆栈跟踪,丢失有价值的上下文。

  • 举例说明:

catch (Exception ex)
{
    // Incorrect
    throw ex; // Rewrites stack trace
}
C#

2.抛出新异常

  • 使用自定义消息创建新异常,但会丢失原始堆栈跟踪。

  • 举例说明:

catch (Exception ex)
{
    // Incorrect
    throw new Exception("I blew up");
}
C#

在不丢失原始堆栈跟踪的情况下创建新异常

Tim 解释了如何在保留原始堆栈跟踪的同时创建新的异常。 当您想提供更有意义的错误信息或不同的异常类型,同时又想保留原始错误的上下文时,这将非常有用。

catch (Exception ex)
{
    throw new ArgumentException("You passed in bad data", ex);
}

通过传递原始异常 (ex) 作为内部异常,可以保留原始堆栈跟踪,这对调试至关重要。

保留堆栈跟踪信息

Tim 演示了在创建新异常时如何访问原始异常消息和堆栈跟踪。

catch (Exception ex)
{
    Console.WriteLine("You passed in bad data");
    Console.WriteLine(ex.StackTrace);
    throw new ArgumentException("You passed in bad data", ex);
}

这样可以确保堆栈中抛出的异常包含新信息和原始异常详细信息。

通过内部异常循环

Tim 提供了一种在所有内部异常中循环以提取其消息和堆栈跟踪的方法。

catch (Exception ex)
{
    Exception inner = ex;
    while (inner != null)
    {
        Console.WriteLine(inner.StackTrace);
        inner = inner.InnerException;
    }
    throw;
}

该循环迭代每个内部异常,打印其堆栈跟踪,确保所有层级的异常都得到考虑。

以不同的方式处理不同的异常

Tim 讨论了如何使用多个捕获块处理不同类型的异常。 这样可以根据异常类型进行特定处理。

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

在此示例中,ArgumentException 通过打印自定义消息进行专门处理,而所有其他异常则返回到打印异常消息和堆栈跟踪的通用处理程序。

多个捕获块中顺序的重要性

Tim 强调了使用多个捕获块时顺序的重要性。 应首先捕捉最特殊的异常,然后再捕捉更一般的异常。

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

如果在特定捕获块之前出现一个更通用的捕获块,它将捕获所有异常,而特定捕获块将永远不会被捕获,从而导致编译错误。

结论

Tim Corey 关于 C# 中异常处理的高级 视频指南涵盖了创建新异常、保留堆栈跟踪和有效使用多个捕获块的关键技术。 通过遵循他的最佳实践,开发人员可以创建强大的应用程序,优雅地处理异常并提供有价值的调试信息。

Earn More by Sharing What You Love

Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Let's Stay in Touch!

Join our newsletter, you’ll get exclusive access on article updates. We value your privacy

Key in blue circle

立即获取免费的 30 天试用版密钥

Your trial license will be sent to your email address

无任何限制。100% 解锁。无需信用卡。

OR
bullet_checked无需信用卡或创建账户无任何限制。100% 解锁。无需信用卡。
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried Iron Suite
获取您的无义务咨询
填写下面的表格或通过sales@ironsoftware.com
您的资料将始终保密。
深受全球数百万工程师信赖
Iron Software 的客户徽标
立即获取您的免费30 天试用密钥
无需信用卡或创建账户