VB Visual Basic 2026 中文教程
第 22 课 · 稳定程序与问题诊断

异常处理与调试

输入验证可以阻止很多可预期错误,但文件可能突然不存在、用户可能没有访问权限,程序也可能包含逻辑错误。异常处理让应用在运行时发生问题时能够合理回应;调试工具则帮助开发者找出程序为什么没有按照预期执行。

适合:完成第 21 课的初学者建议时间:60–75 分钟实作:SafeFileViewer2026

本课学习目标

  • 理解错误、异常和逻辑错误的基本区别。
  • 使用 Try...Catch 处理运行时异常。
  • 使用 Finally 执行无论成功或失败都需要进行的工作。
  • 读取 Exception.Message
  • 认识 FileNotFoundExceptionUnauthorizedAccessExceptionIOException 等常见异常。
  • 理解输入验证与异常处理不能互相取代。
  • 认识 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
进入 Try执行正常代码
发生异常?Yes / No
Catch处理错误
继续程序避免直接崩溃

22.3 Exception.Message

Catch 中的:

ex As Exception

代表捕获到的异常对象。最常用的信息之一是:

ex.Message

它会提供异常说明。例如文件路径无效、访问被拒绝等。

给用户看的消息不一定要直接显示所有异常细节 开发阶段 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
具体 Catch 放前面,一般 Catch 放最后 Exception 范围最广。如果把它放在最前面,后面更具体的 Catch 就失去意义。

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。

可以预防的错误,先预防 Try...Catch 不是替代 If、TryParse、File.Exists 的工具。可靠程序通常先做合理验证,再用异常处理保护真正可能失败的操作。

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。

不要把 Throw 当成普通流程控制 如果“没有学生”是程序正常会出现的状态,通常使用 If 更合适。异常应该表示异常情况,而不是普通分支。

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 切换断点

把光标放在代码行,再按 F9,可以加入或移除断点。

点击代码左侧边栏

也可以直接点击编辑器左侧断点区域。

程序暂停后,就可以查看变量当前值,再逐步继续执行。

Visual Studio Debugger — 示例
Dim price As Decimal = 100D
Dim quantity As Integer = 3
Dim total As Decimal = price + quantity lblResult.Text = total.ToString()
Locals
price100
quantity3
total尚未赋值

22.10 Step Over、Step Into 与 Step Out

操作常用键作用
ContinueF5继续运行到下一个断点。
Step OverF10执行当前行;遇到方法调用时不进入方法内部。
Step IntoF11如果当前行调用方法,进入方法内部逐行观察。
Step OutShift+F11运行到当前方法结束并返回调用处。

22.11 Locals 与 Watch

Locals

Locals 窗口显示当前作用域内的局部变量及其值,非常适合检查:

studentName
score
discountRate
filePath

Watch

Watch 可以主动加入你关心的表达式,例如:

students.Count
score >= 50
Path.GetFileName(filePath)

程序暂停时,可以持续观察这些表达式怎样变化。

22.12 一个简单的调试方法

1
先重现问题

明确什么输入、什么操作会产生错误结果。

2
在关键位置设断点

不要到处乱放,先从输入进入计算的位置开始。

3
检查变量值

观察实际值和你预期的值在哪里第一次不同。

4
逐步进入方法

使用 F10 / F11 找到错误逻辑。

5
修正后重新测试

确认原问题解决,也没有破坏其他场景。

22.13 实作:Safe File Viewer 2026

建立新的 Windows Forms 项目:

SafeFileViewer2026

这个项目让用户通过 OpenFileDialog 选择文本文件,然后安全读取内容。它会结合:

  • File.Exists
  • Try...Catch...Finally
  • 具体异常类型
  • 状态显示
  • 断点调试
Safe File Viewer 2026
文件:
C:\Users\...\Documents\notes.txt
操作:
选择文件读取文件清除
状态:读取成功 文件:notes.txt 字符数:128 这里显示文件内容……

加入以下控件和组件:

控件 / 组件Name用途
TextBoxtxtFilePathReadOnly = True
ButtonbtnBrowseText = 选择文件
ButtonbtnReadText = 读取文件
ButtonbtnClearText = 清除
TextBoxtxtOutputMultiline = True;ReadOnly = True;ScrollBars = Both
LabellblStatus显示操作状态
OpenFileDialogdlgOpen选择文本文件

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)

这一行设置断点,然后:

  1. 运行程序并选择一个文件。
  2. 点击“读取文件”。
  3. 程序在断点暂停。
  4. 在 Locals 中检查 filePath
  5. 按 F10 执行 File.ReadAllText。
  6. 检查 content.Length
  7. 继续运行,确认状态栏显示正确。

22.15 初学者常见错误

错误 1

把所有代码都包在巨大 Try 中

Try 应集中保护真正可能失败的操作,否则很难知道哪一段代码发生问题。

错误 2

Catch 后什么都不做

完全忽略异常会让问题更难发现。至少应记录、显示或合理处理。

错误 3

用异常代替正常验证

空白、范围、文件是否存在等可预期情况,应优先用验证逻辑处理。

错误 4

调试时只盯着代码,不看变量

断点最重要的价值之一,就是观察变量“实际是什么”,而不是猜测。

22.16 小练习

  1. 人为把一个不存在的路径放入 txtFilePath,观察 File.Exists 怎样阻止错误。
  2. 暂时移除 File.Exists 检查,再观察 Catch 如何处理异常;完成后恢复验证。
  3. 在 ReadSelectedFile 中加入一个断点,使用 F10 和 F11 比较执行方式。
  4. content.Length 加入 Watch。
  5. 加入日志文件,在 Catch 中记录日期时间和 ex.Message。
  6. 新增一个 Function,故意把两个数字相加而不是相乘,再用断点找出逻辑错误。
  7. 加入 FileNotFoundException 的具体 Catch,并比较它与 IOException 的处理顺序。

本课复习

  1. 编译错误、运行时异常和逻辑错误有什么区别?
  2. Try...Catch 的作用是什么?
  3. ex.Message 提供什么信息?
  4. 为什么具体 Catch 应放在一般 Catch ex As Exception 前面?
  5. Finally 适合做什么?
  6. 为什么 Try...Catch 不能取代 TryParse、If 和 File.Exists?
  7. Throw 的用途是什么?
  8. 断点有什么作用?
  9. F10 与 F11 有什么区别?
  10. Locals 与 Watch 分别适合观察什么?