本课学习目标
- 理解错误、异常和逻辑错误的基本区别。
- 使用 Try...Catch 处理运行时异常。
- 使用 Finally 执行无论成功或失败都需要进行的工作。
- 读取 Exception.Message。
- 认识 FileNotFoundException、UnauthorizedAccessException、IOException 等常见异常。
- 理解输入验证与异常处理不能互相取代。
- 认识 Throw 的基本用途。
- 在 Visual Studio 中设置断点。
- 使用 Step Over、Step Into、Step Out 逐步执行。
- 使用 Locals 与 Watch 检查变量值。
- 完成一个具有异常处理的安全文件查看器。
22.1 程序错误不只有一种
调试的第一步,是先知道你面对的是什么类型的问题。
代码无法编译
例如拼错关键字、类型不匹配或语法不完整。通常在运行前就能发现。
运行过程中失败
例如读取不存在的文件、没有权限访问目录等。
程序能运行但结果错
例如折扣公式写成加法而不是乘法。编译器不一定知道这是错误。
22.2 Try...Catch 的基本结构
Try
' 可能发生异常的代码
Catch ex As Exception
' 发生异常时执行
End Try
例如:
Try
Dim text As String =
File.ReadAllText(filePath)
txtOutput.Text = text
Catch ex As Exception
MessageBox.Show(
ex.Message,
"读取失败",
MessageBoxButtons.OK,
MessageBoxIcon.Error)
End Try
22.3 Exception.Message
Catch 中的:
ex As Exception
代表捕获到的异常对象。最常用的信息之一是:
ex.Message
它会提供异常说明。例如文件路径无效、访问被拒绝等。
22.4 捕获更具体的异常
不同异常可以提供更准确的处理方式:
Try
txtOutput.Text =
File.ReadAllText(filePath)
Catch ex As FileNotFoundException
MessageBox.Show(
"找不到指定文件。")
Catch ex As UnauthorizedAccessException
MessageBox.Show(
"没有权限读取这个文件。")
Catch ex As IOException
MessageBox.Show(
"读取文件时发生 I/O 错误。")
Catch ex As Exception
MessageBox.Show(
"发生未预期错误:" &
ex.Message)
End Try
22.5 Finally
Finally 中的代码,无论 Try 成功还是发生异常,通常都会执行。
Try
lblStatus.Text =
"正在读取..."
txtOutput.Text =
File.ReadAllText(filePath)
Catch ex As Exception
MessageBox.Show(
ex.Message)
Finally
btnOpen.Enabled = True
End Try
常见用途:
- 恢复按钮状态
- 关闭或清理资源
- 恢复鼠标指针
- 执行一定要完成的清理工作
22.6 输入验证与异常处理不是同一件事
处理可预期的用户错误,例如空白、非数字、成绩超出 0–100。
处理运行过程中可能发生、但不是普通业务分支的问题,例如文件权限、磁盘 I/O、系统异常。
应该先验证
If String.IsNullOrWhiteSpace(
txtFilePath.Text) Then
MessageBox.Show(
"请选择文件。")
Exit Sub
End If
而不是故意让空路径进入 File.ReadAllText,再依赖 Catch。
22.7 Throw 简介
有时候方法发现业务条件不成立,可以主动抛出异常:
Private Function CalculateAverage(
total As Double,
count As Integer) As Double
If count = 0 Then
Throw New ArgumentException(
"count 不能为 0。")
End If
Return total / count
End Function
调用者可以在更合适的位置 Catch。
22.8 什么是调试?
调试(Debugging)是观察程序实际执行过程,找出为什么结果和预期不同。
例如:
Dim price As Decimal = 100D
Dim quantity As Integer = 3
Dim total As Decimal =
price + quantity
程序可以运行,但 total = 103,而我们真正想要的可能是 300。这就是典型逻辑错误。
22.9 设置断点
断点(Breakpoint)会让程序运行到指定代码行时暂停。
常用方式:
把光标放在代码行,再按 F9,可以加入或移除断点。
也可以直接点击编辑器左侧断点区域。
程序暂停后,就可以查看变量当前值,再逐步继续执行。
Dim quantity As Integer = 3
Dim total As Decimal = price + quantity lblResult.Text = total.ToString()
22.10 Step Over、Step Into 与 Step Out
| 操作 | 常用键 | 作用 |
|---|---|---|
| Continue | F5 | 继续运行到下一个断点。 |
| Step Over | F10 | 执行当前行;遇到方法调用时不进入方法内部。 |
| Step Into | F11 | 如果当前行调用方法,进入方法内部逐行观察。 |
| Step Out | Shift+F11 | 运行到当前方法结束并返回调用处。 |
22.11 Locals 与 Watch
Locals
Locals 窗口显示当前作用域内的局部变量及其值,非常适合检查:
studentName
score
discountRate
filePath
Watch
Watch 可以主动加入你关心的表达式,例如:
students.Count
score >= 50
Path.GetFileName(filePath)
程序暂停时,可以持续观察这些表达式怎样变化。
22.12 一个简单的调试方法
明确什么输入、什么操作会产生错误结果。
不要到处乱放,先从输入进入计算的位置开始。
观察实际值和你预期的值在哪里第一次不同。
使用 F10 / F11 找到错误逻辑。
确认原问题解决,也没有破坏其他场景。
22.13 实作:Safe File Viewer 2026
建立新的 Windows Forms 项目:
SafeFileViewer2026
这个项目让用户通过 OpenFileDialog 选择文本文件,然后安全读取内容。它会结合:
- File.Exists
- Try...Catch...Finally
- 具体异常类型
- 状态显示
- 断点调试
加入以下控件和组件:
| 控件 / 组件 | Name | 用途 |
|---|---|---|
| TextBox | txtFilePath | ReadOnly = True |
| Button | btnBrowse | Text = 选择文件 |
| Button | btnRead | Text = 读取文件 |
| Button | btnClear | Text = 清除 |
| TextBox | txtOutput | Multiline = True;ReadOnly = True;ScrollBars = Both |
| Label | lblStatus | 显示操作状态 |
| OpenFileDialog | dlgOpen | 选择文本文件 |
Form1.vb 顶部
Imports System.IO
Public Class Form1
选择文件
Private Sub btnBrowse_Click(
sender As Object,
e As EventArgs) Handles btnBrowse.Click
dlgOpen.Filter =
"文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"
dlgOpen.Title =
"选择要读取的文件"
If dlgOpen.ShowDialog() =
DialogResult.OK Then
txtFilePath.Text =
dlgOpen.FileName
lblStatus.Text =
"已选择文件。"
End If
End Sub
安全读取方法
Private Sub ReadSelectedFile()
Dim filePath As String =
txtFilePath.Text.Trim()
If String.IsNullOrWhiteSpace(
filePath) Then
MessageBox.Show(
"请先选择文件。",
"提示",
MessageBoxButtons.OK,
MessageBoxIcon.Information)
Exit Sub
End If
If Not File.Exists(filePath) Then
MessageBox.Show(
"指定文件不存在。",
"文件错误",
MessageBoxButtons.OK,
MessageBoxIcon.Warning)
Exit Sub
End If
btnRead.Enabled = False
lblStatus.Text = "正在读取..."
Try
Dim content As String =
File.ReadAllText(filePath)
txtOutput.Text =
content
lblStatus.Text =
$"读取成功:{Path.GetFileName(filePath)}," &
$"字符数 {content.Length}"
Catch ex As UnauthorizedAccessException
lblStatus.Text =
"读取失败:没有访问权限。"
MessageBox.Show(
"没有权限读取这个文件。",
"访问被拒绝",
MessageBoxButtons.OK,
MessageBoxIcon.Error)
Catch ex As IOException
lblStatus.Text =
"读取失败:I/O 错误。"
MessageBox.Show(
"读取文件时发生 I/O 错误。" &
Environment.NewLine &
ex.Message,
"文件错误",
MessageBoxButtons.OK,
MessageBoxIcon.Error)
Catch ex As Exception
lblStatus.Text =
"读取失败:未预期错误。"
MessageBox.Show(
"发生未预期错误。" &
Environment.NewLine &
ex.Message,
"错误",
MessageBoxButtons.OK,
MessageBoxIcon.Error)
Finally
btnRead.Enabled = True
End Try
End Sub
读取按钮
Private Sub btnRead_Click(
sender As Object,
e As EventArgs) Handles btnRead.Click
ReadSelectedFile()
End Sub
清除按钮
Private Sub btnClear_Click(
sender As Object,
e As EventArgs) Handles btnClear.Click
txtFilePath.Clear()
txtOutput.Clear()
lblStatus.Text =
"Ready"
End Sub
22.14 用调试器检查这个项目
建议在:
Dim content As String =
File.ReadAllText(filePath)
这一行设置断点,然后:
- 运行程序并选择一个文件。
- 点击“读取文件”。
- 程序在断点暂停。
- 在 Locals 中检查 filePath。
- 按 F10 执行 File.ReadAllText。
- 检查 content.Length。
- 继续运行,确认状态栏显示正确。
22.15 初学者常见错误
把所有代码都包在巨大 Try 中
Try 应集中保护真正可能失败的操作,否则很难知道哪一段代码发生问题。
Catch 后什么都不做
完全忽略异常会让问题更难发现。至少应记录、显示或合理处理。
用异常代替正常验证
空白、范围、文件是否存在等可预期情况,应优先用验证逻辑处理。
调试时只盯着代码,不看变量
断点最重要的价值之一,就是观察变量“实际是什么”,而不是猜测。
22.16 小练习
- 人为把一个不存在的路径放入 txtFilePath,观察 File.Exists 怎样阻止错误。
- 暂时移除 File.Exists 检查,再观察 Catch 如何处理异常;完成后恢复验证。
- 在 ReadSelectedFile 中加入一个断点,使用 F10 和 F11 比较执行方式。
- 把 content.Length 加入 Watch。
- 加入日志文件,在 Catch 中记录日期时间和 ex.Message。
- 新增一个 Function,故意把两个数字相加而不是相乘,再用断点找出逻辑错误。
- 加入 FileNotFoundException 的具体 Catch,并比较它与 IOException 的处理顺序。
本课复习
- 编译错误、运行时异常和逻辑错误有什么区别?
- Try...Catch 的作用是什么?
- ex.Message 提供什么信息?
- 为什么具体 Catch 应放在一般 Catch ex As Exception 前面?
- Finally 适合做什么?
- 为什么 Try...Catch 不能取代 TryParse、If 和 File.Exists?
- Throw 的用途是什么?
- 断点有什么作用?
- F10 与 F11 有什么区别?
- Locals 与 Watch 分别适合观察什么?