ZetCode

C# 异常

最后修改于 2023 年 7 月 5 日

本文介绍了如何在 C# 中处理异常。

异常表示应用程序执行期间发生的错误。

在应用程序执行期间,可能会发生很多错误。磁盘可能已满,我们无法保存文件。当我们的应用程序尝试连接到某个站点时,Internet 连接可能会中断。这些错误可能会导致问题,包括应用程序崩溃。程序员有责任处理可以预测的错误。

trycatchfinally 关键字用于处理异常。

Exception 是所有异常的基类。错误通过抛出异常来报告。抛出异常后,它由应用程序或默认异常处理程序处理。该异常包含有关错误的信息。

在应用程序开发期间和开发完成后,会抛出不同类型的异常。开发人员会收到包含大量技术细节的异常,而用户只会收到简短的基本信息消息。

C# 捕获异常

catch 关键字用于捕获抛出的异常。

Program.cs
int x = 100;
int y = 0;
int z;

try
{
    z = x / y;
}
catch (ArithmeticException e)
{
    Console.WriteLine("An exception occurred");
    Console.WriteLine(e.Message);
}

在上面的程序中,我们故意将一个数字除以零。这会导致错误。请注意,这是一个学术示例,用于演示异常如何工作。实际上,除以错误是一个程序错误,可以通过确保分母不为零来解决。

try
{
    z = x / y;
}

容易出错的语句放在 try 块中。

catch (ArithmeticException e)
{
    Console.WriteLine("An exception occurred");
    Console.WriteLine(e.Message);
}

异常类型跟在 catch 关键字之后。在我们的例子中,我们有一个 ArithmeticException。为算术、强制转换或转换操作中的错误抛出此异常。当发生错误时,执行跟在 catch 关键字之后的语句。当发生异常时,会创建一个异常对象。我们从该对象中获取 Message 属性并将其打印到控制台。

$ dotnet run
An exception occurred
Attempted to divide by zero.

C# 未捕获的异常

当前上下文中任何未捕获的异常都会传播到更高的上下文,并查找适当的 catch 块来处理它。如果找不到任何合适的 catch 块,.NET 运行时的默认机制将终止整个程序的执行。

Program.cs
int x = 100;
int y = 0;

int z = x / y;

Console.WriteLine(z);

在此程序中,我们将除以零。没有自定义异常处理。

$ dotnet run
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
...

C# 编译器给出了上述错误消息。

C# IOException

当发生 I/O 错误时,会抛出 IOException。在下面的示例中,我们读取文件的内容。

Program.cs
var fs = new FileStream("langs.txt", FileMode.OpenOrCreate);

try
{
    var sr = new StreamReader(fs);
    string? line;

    while ((line = sr.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }

}
catch (IOException e)
{
    Console.WriteLine("IO Error");
    Console.WriteLine(e.Message);
}
finally
{
    Console.WriteLine("Inside finally block");

    if (fs != null)
    {
        fs.Close();
    }
}

finally 关键字后面的语句始终执行。它通常用于清理任务,例如关闭文件或清除缓冲区。

catch (IOException e)
{
    Console.WriteLine("IO Error");
    Console.WriteLine(e.Message);
}

在这种情况下,我们捕获特定的 IOException 异常。

finally
{
    Console.WriteLine("Inside finally block");

    if (fs != null)
    {
        fs.Close();
    }
}

这些行保证文件句柄已关闭。

$ cat langs.txt
C#
Java
Python
Ruby
PHP
JavaScript

这些是 langs.txt 文件的内容。

$ dotnet run
C#
Java
Python
Ruby
PHP
JavaScript
Inside finally block

C# 多个异常

我们经常需要处理多个异常。

Program.cs
int x;
int y;
double z;

try
{
    Console.Write("Enter first number: ");
    x = Convert.ToInt32(Console.ReadLine());

    Console.Write("Enter second number: ");
    y = Convert.ToInt32(Console.ReadLine());

    z = x / y;
    Console.WriteLine("Result: {0:N} / {1:N} = {2:N}", x, y, z);
}
catch (DivideByZeroException e)
{
    Console.WriteLine("Cannot divide by zero");
    Console.WriteLine(e.Message);

}
catch (FormatException e)
{
    Console.WriteLine("Wrong format of number.");
    Console.WriteLine(e.Message);
}

在此示例中,我们捕获各种异常。请注意,更具体的异常应位于通用异常之前。我们从控制台读取两个数字,并检查零除错误和错误的数字格式。

$ dotnet run
Enter first number: we
Wrong format of number.
Input string was not in a correct format.

C# 自定义异常

自定义异常是用户定义的异常类,它派生自 System.Exception 类。

Program.cs
int x = 340004;
const int LIMIT = 333;

try
{
    if (x > LIMIT)
    {
        throw new BigValueException("Exceeded the maximum value");
    }
}
catch (BigValueException e)
{
    Console.WriteLine(e.Message);
}

class BigValueException : Exception
{
    public BigValueException(string msg) : base(msg) { }
}

我们假设我们遇到无法处理大数字的情况。

class BigValueException : Exception

我们有一个 BigValueException 类。该类派生自内置的 Exception 类。

const int LIMIT = 333;

大于此常量的数字被我们的程序认为是“大”数字。

public BigValueException(string msg) : base(msg) {}

在构造函数内部,我们调用父类的构造函数。我们将消息传递给父类。

if (x > LIMIT)
{
    throw new BigValueException("Exceeded the maximum value");
}

如果该值大于限制,我们将抛出我们的自定义异常。我们给异常一个消息“超过最大值”。

catch (BigValueException e)
{
    Console.WriteLine(e.Message);
}

我们捕获异常并将其消息打印到控制台。

$ dotnet run
Exceeded the maximum value

来源

异常和异常处理

本文介绍了如何在 C# 中处理异常。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 C# 教程