1. 入门
理想情况下,代码应该是完美的。但现实是,即使是最细心的程序员也总会遇到意外情况:比如找不到文件,用户输入了字符串而不是数字,网络突然断了,或者数据库返回的东西和预期不一样。
如果这些问题没人管——程序就会直接“崩溃”,有时候还会冒出一堆神秘的错误信息和stack trace(调用堆栈)。这可不是用户或者未来的技术支持想看到的。我们得学会“抓住”这些错误并做出反应:优雅地结束程序,输出人类能看懂的提示,甚至有时候还能现场修复问题。
try-catch——就是告诉编译器和运行环境:“试试执行这段代码。如果出问题了,别慌,让我来处理!”
2. try-catch的基本语法
整体结构其实很简单:
try
{
// 这里写危险(或者可能有风险)的代码
}
catch (ExceptionType variableName)
{
// 这里写遇到ExceptionType类型异常时要干嘛
}
- try块——就是“危险区”。把那些可能“出事儿”的操作放这里。
- catch块——捕获特定类型的异常。如果try里抛出异常,程序会立刻跳到第一个匹配的catch,而try里“出事儿”后面的代码就不会再执行了。
例子:简单的异常处理
假设我们之前讲过的小型计算器现在能做除法了。但用户可能会输入0!看看没处理错误时会发生什么:
int a = 10;
int b = 0;
int result = a / b; // 砰!System.DivideByZeroException
Console.WriteLine(result);
程序会因为DivideByZeroException异常直接崩溃:
未处理的异常。System.DivideByZeroException: 尝试除以零。
现在用try-catch来“修一下”:
int a = 10;
int b = 0;
try
{
int result = a / b; // 危险操作
Console.WriteLine("除法结果: " + result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("哎呀!不能除以零: " + ex.Message);
}
现在程序不会“崩溃”了。控制台会显示不能除以零的提示:
哎呀!不能除以零: 尝试除以零。
3. try-catch是怎么工作的:一步一步看
其实try里可以写很多行代码。如果其中某个操作抛出异常,程序会立刻跳到最近的、匹配的catch。在try块里出错后的代码都不会再执行。
来看个大一点的例子:
try
{
Console.WriteLine("开始...");
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[1]); // 没问题
Console.WriteLine(numbers[5]); // 错误:数组越界
Console.WriteLine("这条消息不会出现了!");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("错误:试图访问不存在的数组元素");
}
控制台输出会是这样:
开始...
2
错误:试图访问不存在的数组元素
Process finished with exit code 0.
这个程序是怎么运行的?
- 先输出“开始...”
- 然后程序试图输出numbers[1]——就是2。
- 当执行到numbers[5]时,“炸了”,抛出IndexOutOfRangeException。
- 程序立刻跳到catch块,Console.WriteLine("这条消息不会出现了!");被跳过。
- 控制台会显示catch里的消息。
4. 能不能对不同类型的错误分别处理?
当然可以!我们可以写多个catch块,对不同类型的异常做不同处理。很方便,比如一个操作是读文件(可能会有FileNotFoundException),另一个是除法(可能会有DivideByZeroException)。
try
{
// 你的危险代码
}
catch (DivideByZeroException)
{
Console.WriteLine("除以零错误");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("错误:数组索引越界");
}
catch (Exception ex)
{
Console.WriteLine("遇到未知错误: " + ex.Message);
}
这里最后一个catch类型是Exception——“万能捕手”。它会捕获前面没被抓到的所有异常。但注意:如果你把它写在最前面,后面的catch就永远不会被执行!所以“宽泛”的catch要放最后。
5. 异常对象长啥样?
在catch块里我们可以选填一个变量——比如catch (Exception ex)。它包含了所有关于错误的信息:消息、错误类型、调用堆栈、嵌套异常等。
来看个小例子:
try
{
string? text = null;
Console.WriteLine(text.Length); // 哎呦!NullReferenceException
}
catch (NullReferenceException ex)
{
Console.WriteLine("捕获到异常: " + ex.Message);
Console.WriteLine("调用堆栈: " + ex.StackTrace);
}
这种方式在调试复杂错误时特别有用:你总能看到程序“摔倒”在哪儿、为啥摔倒。
6. 抓住错误后继续运行
在真实的、尤其是面向用户的应用里,出错并不是世界末日。比如用户输错了文件路径,这很正常。这还不至于让程序“崩溃”重启。我们可以让他再试一次。
下面这个例子展示了一个简单但实用的做法:我们把读文件的操作放在循环里,如果出错就提示用户重试。没有恐慌,没有红色报错屏。
bool success = false;
while (!success)
{
Console.Write("请输入文件名: ");
string fileName = Console.ReadLine() ?? "";
try
{
string content = File.ReadAllText(fileName);
Console.WriteLine("文件读取成功!");
success = true; // 太棒了!
}
catch (FileNotFoundException)
{
Console.WriteLine("错误:文件未找到。请再试一次。");
}
}
这里的核心思想——不是简单地捕获异常,而是合理地处理它:我们不让程序崩溃,而是切换到“再来一次”的模式。用户得到了清晰的提示,程序有了第二次机会,你也会收获感谢和尊重。
7. 用try-catch时常见的错误
错误1:把所有代码都包进一个万能try-catch里。
这样看起来很省事——一个try,一个catch (Exception),好像“全都安全了”。但实际上你会失去对问题的掌控。根本不知道到底哪儿出错了,也不知道你到底在处理什么异常。结果不是更安全,反而彻底丢失了诊断能力。
错误2:捕获异常却啥也不做。
生活中的例子:
try
{
// 可能有风险的操作
}
catch
{
// 什么都不说...
}
这种代码的意思就是:“出事儿了...算了。” 这很危险:程序可能会在错误状态下继续运行,你甚至都不知道出过错。至少要输出个提示或者记录下错误。千万别写空的
catch!
错误3:捕获过于宽泛的异常类型。
如果你捕获Exception,但其实你知道可能会出现FormatException或者FileNotFoundException,——你就失去了针对具体问题做出合适反应的机会。你的catch越精确,程序在特殊情况下就越可控、越靠谱。
8. 不推荐用异常做什么
有时候新手会把异常当成...普通的逻辑控制手段,比如:
try
{
if (x < 0) throw new Exception("x必须>=0");
}
catch (Exception)
{
// 回滚状态,继续运行...
}
千万别这样!异常是用来处理意外、罕见的情况(比如读文件失败、网络故障等),不是用来做普通的用户数据校验的。遇到这种情况请用条件语句(if、else)和特殊返回值来处理。
GO TO FULL VERSION