VB.NET Finally块关闭StreamWriter报NullReferenceException的解决方法
测试复现代码
Module Program Sub Main(args As String()) Dim sFileName As String = "VBnet_TextFileIO.txt" Dim sTextToWrite As String = "Hello From VB.net" + vbCrLf + "Second Line " Dim myFileStreamWriter As StreamWriter = Nothing Try myFileStreamWriter = New StreamWriter(sFileName, True) myFileStreamWriter.WriteLine(sTextToWrite) myFileStreamWriter.Close() Catch ex As Exception Console.WriteLine("An Exception Occurred") Finally myFileStreamWriter.Close() ' 此处触发空引用 End Try End Sub End Module
复现条件:将目标写入文件设为只读属性,触发System.UnauthorizedAccessException后,Finally块执行时抛出空引用异常。
1. 空引用异常的具体原因
首先排除作用域猜想:VB.NET中方法级声明的变量,在同方法的Try/Catch/Finally块内都可正常访问,不存在Finally块超出变量作用域的情况。
异常的根本原因:
- 声明
myFileStreamWriter时,变量初始值被显式设为Nothing - 目标文件为只读时,
New StreamWriter(sFileName, True)这行构造函数执行阶段就会直接抛出权限异常,流对象根本没有创建成功,给myFileStreamWriter赋值的操作完全没有执行,变量始终保持初始的Nothing值 - 异常抛出后代码直接跳转进入Catch块,再执行Finally逻辑,此时对值为
Nothing的变量直接调用.Close()方法,必然触发空引用。
补充说明:如果异常发生在
WriteLine执行阶段(比如磁盘满、文件被其他进程占用),此时StreamWriter已经构造完成,myFileStreamWriter不为Nothing,Finally块的Close不会触发空引用,这也是该问题只有在构造阶段抛异常时才会复现的原因。
2. 现有代码的最小修复方案
只需要在Finally块调用Close/Dispose前,增加非空判断即可。由于StreamWriter的Dispose方法做了幂等处理,重复调用已释放对象的Close/Dispose不会触发异常,不需要额外加状态判断:
Finally ' 仅当对象实例存在时才执行释放 If myFileStreamWriter IsNot Nothing Then myFileStreamWriter.Dispose() End If End Try
说明:
StreamWriter.Close()方法本质是内部调用Dispose()方法,直接调用Dispose和调用Close效果一致。
3. 流对象的正确编码规范(避免资源泄漏与异常)
所有实现了IDisposable接口的非托管资源(包括文件流、StreamReader/Writer、数据库连接、图像对象等),优先使用Using语句管理生命周期,不要手动写Try-Finally释放。Using是VB.NET专门为资源释放设计的语法糖,编译时会自动生成可靠的try/finally块,自动完成非空判断、资源释放逻辑,无论代码块是正常执行结束还是中途抛出异常,都会保证资源被正确释放,从语法层面避免手动写释放逻辑的疏漏。
改写后的正确代码示例:
Module Program Sub Main(args As String()) Dim sFileName As String = "VBnet_TextFileIO.txt" Dim sTextToWrite As String = "Hello From VB.net" + vbCrLf + "Second Line " Try ' Using声明的对象会在代码块结束时自动释放,无需手动调用Close/Dispose Using myFileStreamWriter As New StreamWriter(sFileName, True) myFileStreamWriter.WriteLine(sTextToWrite) End Using ' 离开Using块时自动执行释放,异常场景下也会正常执行 Catch ex As Exception Console.WriteLine($"An Exception Occurred: {ex.Message}") End Try End Sub End Module
额外编码注意事项:
- 捕获异常时建议输出具体异常信息,不要只打印通用错误提示,方便快速定位问题是权限不足、文件占用还是路径非法
- 不要在Try块内提前手动调用Close/Dispose,Using块会自动完成释放,重复调用不会出错但属于冗余代码
- 禁止声明IDisposable对象后不做任何释放管理,否则会导致文件句柄泄漏、内存占用不释放等问题。
内容的提问来源于stack exchange,提问作者Rahul.In

