ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

VB2012随书源码跑通指南:框架版本、批量扫描与逐章验证

VB2012随书源码跑通指南:框架版本、批量扫描与逐章验证

简介:这份资源是《Visual Basic 2012入门经典(第7版中文版)》的配套随书源码,面向刚接触VB 2012与.NET桌面开发的初学者,也适合需要对照书中示例巩固语法和项目结构的读者。源码围绕基础语法、流程控制、面向对象、窗体控件与事件处理展开,并涉及ADO.NET数据库访问、异常处理、文件读写、简单网络通信及调试排错等章节练习,便于边读边动手复现示例、理解事件驱动编程思路。压缩包约10.15MB,包内文件总数当前显示为0,主要文件类型暂无数据,具体包体情况建议以下载页实际内容为准。已有319人学习或下载,可作为课程练习、自学跟练和查漏补缺的参考材料。

1. 解压一份 2012 年的随书源码,第一眼该看什么

解压《Visual Basic 2012入门经典(第7版)中文版》的随书源码,第一反应往往不是捡到宝,而是对着几十个按章编号的文件夹发呆:控制台、WinForms 窗体、数据绑定、绘图、部署各占一摊,每个目录里还压着 bin 和 obj。这类随书源码不是拿来通读的,它是一份可以逐章验证的语法索引——书里讲 Dim、Handles、LINQ、ADO.NET 的那几页,对应目录里就有一个能按 F5 跑起来的最小工程。它能解决的问题很具体:把“看懂了”变成“跑通了”。适合三类人:刚学 VB.NET 需要现成参照的自学者、接手老 WinForms 项目要快速找回手感的人、想拿小工程练手又懒得从空解决方案搭架子的人。

2. 拆开压缩包先别双击:目录组织与工程文件清单

2.1 章节编号、解决方案和项目是三层层级

随书源码的组织方式几乎一个模子:顶层是按章编号的文件夹(Ch02、Ch05 之类),往下一层才是*.sln,再往下一层才是*.vbproj。问题在于,并不是每一章都遵守“一章一个解决方案”的约定——讲语法的章节常常一个解决方案里挂七八个项目,每个示例一个;讲综合应用的章节反过来,一个解决方案只挂一个大项目,多个窗体轮流演示。所以正确的做法是先用文件搜索把.sln和.vbproj的数量摸清楚,再决定从哪一章切入。

我一般会先跑一条命令搞清楚全貌,而不是凭目录名猜:

# 统计随书源码里解决方案与项目的分布,先看清结构再动手 $root = "D:\vb2012src" Get-ChildItem $root -Recurse -Filter *.sln | ForEach-Object { $projCount = (Select-String -Path $_.FullName -Pattern '^Project\(' -AllMatches).Count [pscustomobject]@{ Solution = $_.Name Folder = $_.Directory.Name Projects = $projCount } } | Sort-Object Folder | Format-Table -AutoSize

-Filter *.sln比-Include快得多,因为过滤发生在文件系统层而不是 PowerShell 层;Select-String数Project(开头的行,正好对应解决方案文件里每个项目的声明行,Projects为 0 说明这个 sln 是空壳或者引用的项目路径已经断了。跑完这张表,你会看到两种极端:有的章一个 sln 挂十几个项目,有的章 sln 里一个项目都没有——后者基本可以判定是从光盘拷贝时漏了目录,不用浪费时间。

接下来要认的是文件类型。随书源码里会混进大量“看起来像源码其实不该动”的文件,动手前先把它们分清楚:

扩展名真实身份要不要动
.sln解决方案,记录项目清单与生成配置只读,不要手改
.vbprojMSBuild 项目文件,决定框架版本和输出类型必要时改框架版本
.vb真正的源码,语法示例都在这可以改,建议先另存
.designer.vb设计器自动生成,记录控件坐标与属性绝对不要手改
.resx窗体资源,图片、字符串、图标都在里面只在设计器里改
.configapp.config,连接字符串和绑定重定向数据类示例必须看
.mdf/.sdf示例数据库文件只读,先复制一份再用
.suo/.userIDE 的个人设置缓存直接删掉

.suo和.user这两个最容易被忽略。它们是上一个使用者机器上的窗口布局、断点、上次打开的文档记录,跨机器打开时设计器崩溃、调试器行为诡异,有一半是这两个文件在作祟。解压后第一件事就是把它们全删了,成本为零。

2.2 一份 .vbproj 里真正要看的只有四行

教材配套的项目文件是用 VS 2012 生成的,格式是 MSBuild 的 XML,几百行里绝大多数是编译选项的默认值。真正影响“能不能跑起来”的只有四个节点:

<PropertyGroup> <!-- 目标框架:多数示例是 v4.5,少数早期章节还停在 v4.0 --> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <!-- 输出类型:Exe 是控制台,WinExe 是窗体,Library 是类库不能直接 F5 --> <OutputType>WinExe</OutputType> <!-- 启动对象:窗体程序写"命名空间.窗体名",控制台写 Sub Main 所在模块 --> <StartupObject>WindowsApplication1.Form1</StartupObject> <!-- 平台目标:老示例里有锁 x86 的,和 AnyCPU 混在一起会出怪问题 --> <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup>

TargetFrameworkVersion决定这台机器上有没有对应的 targeting pack,没有就直接报“未安装目标框架”,这是新手卡住的第一个点。OutputType决定 F5 之后是弹黑窗口还是弹窗体,也决定了StartupObject该写什么——类库项目没有启动对象,你按 F5 只会得到“无法直接启动类库”的提示,这不是代码坏了。PlatformTarget在纯托管代码里影响不大,但只要示例里引用了Microsoft.VisualBasic.PowerPacks或者早期的 Access 驱动,x86 和 AnyCPU 的差别就会以“BadImageFormatException”的形式冒出来。

这里有一个省事的判断法:如果OutputType是Library,说明这一章讲的是类、接口、继承之类的纯语法,最佳用法是把它的.vb文件当成代码片段看,而不是指望跑起来;如果是WinExe但项目里只有一个.vb文件加一个.designer.vb,那它多半是单窗体 Demo,风险最低,适合拿来验证环境。

2.3 建立本地工作区:短路径、去只读、清残留

从压缩包里直接双击打开.sln是翻车率最高的操作。原因不玄学:很多随书源码是直接刻盘再打包的,解压出来的文件带着只读属性;压缩包的深层目录结构还会把路径撑到 200 字符以上,MSBuild 处理长路径时容易在中间产物上失手。我的习惯是先规整到一个短路径,再统一处理属性:

$root = "D:\vb2012src" # 1. 去掉所有文件的只读属性,否则编译时写不进 bin 和 obj Get-ChildItem $root -Recurse -File -Force | ForEach-Object { $_.IsReadOnly = $false } # 2. 清掉随书源码里残留的 bin/obj,避免旧的 dll 干扰新编译 Get-ChildItem $root -Recurse -Directory -Force | Where-Object { $_.Name -in @('bin', 'obj') } | Remove-Item -Recurse -Force # 3. 删掉 IDE 缓存,跨机器打开时必须做 Get-ChildItem $root -Recurse -Include *.suo, *.user -Force | Remove-Item -Force

第 1 步用-File限定只处理文件,因为IsReadOnly属性只在FileInfo上有意义,对目录调用会报错;第 2 步用-in做集合匹配,比一串-or干净;第 3 步的-Include必须配合-Recurse才能匹配到子目录里的文件,这一点经常被写错成只匹配当前目录。

做完这三步再打开解决方案,能省掉后面一大半莫名其妙的“访问被拒绝”和“找不到类型”。工作区目录也不要选桌面或者带中文和空格的路径,虽然 VS 2012 之后的版本对中文路径容忍度提高了,但随书源码里有些示例的硬编码相对路径是按英文目录写的,路径一变就读不到资源文件。

3. 让 .sln 真正跑起来:框架版本、批量扫描与逐章验证

3.1 目标框架对不上时的三步处理顺序

打开解决方案最常见的拦路虎是“该项目面向 .NET Framework 4.5,但本机未安装对应的开发工具”。注意这里缺的不是运行时,是targeting pack(开发时用的引用程序集)。运行时版本可以向上兼容——装了 4.8 的机器能跑 4.5 编译出来的程序——但 VS 要编译一个 4.5 目标的项目,必须能找到 4.5 的引用程序集。

处理顺序是有讲究的,不要一上来就改项目文件:

  1. 先确认本机装了哪些 targeting pack,路径在C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\下面,目录名就是版本号。
  2. 如果只是缺 4.5,优先装 4.5 的 Developer Pack,保持项目原样,这样源码里的所有行为和书上一致。
  3. 如果不想装旧包,就把TargetFrameworkVersion改成机器上已有的版本(一般是v4.8),但要接受一个代价:编译器会按新版本的规则检查,个别老写法可能报新的警告,极少数示例里用到的过时 API 会直接编译失败。

改版本的正确姿势是用 IDE 的项目属性页,而不是文本编辑器硬改。原因在于改框架版本时 IDE 会同步更新app.config里的supportedRuntime和绑定重定向,手工改 XML 容易漏掉这些,结果就是编译过了、运行时加载程序集失败。

<!-- 手工改版本时,app.config 里这一节必须同步,否则运行期找不到程序集 --> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <!-- 老示例引用的 System.Data 版本号可能停留在 4.0.0.0 --> <dependentAssembly> <assemblyIdentity name="System.Data" publicKeyToken="b77a5c561934e089" /> <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>

sku那一行写的必须和实际装的版本一致,写成v4.5但机器只有 4.8,程序会以兼容模式启动,某些新 API 调用会在运行期抛异常而不是编译期报错,这种问题最难查。bindingRedirect则是给那些引用了强命名程序集的老示例兜底的,尤其是数据访问章节,System.Data的版本号一变就是“未能加载文件或程序集”。

3.2 用一条脚本把所有项目的框架版本摸清楚

与其一个个打开项目属性页看,不如一次扫完。这条脚本我在每个随书源码包里都会跑一遍:

# 扫描所有 vbproj,输出框架版本、输出类型、启动对象,按版本分组 Get-ChildItem "D:\vb2012src" -Recurse -Filter *.vbproj | ForEach-Object { $text = Get-Content $_.FullName -Raw -Encoding UTF8 [pscustomobject]@{ Project = $_.BaseName Chapter = $_.Directory.Parent.Name Target = [regex]::Match($text, '<TargetFrameworkVersion>(.*?)</TargetFrameworkVersion>').Groups[1].Value Output = [regex]::Match($text, '<OutputType>(.*?)</OutputType>').Groups[1].Value Startup = [regex]::Match($text, '<StartupObject>(.*?)</StartupObject>').Groups[1].Value } } | Sort-Object Target, Chapter | Format-Table -AutoSize

-Raw把整个文件读成一个字符串,比逐行读再拼接快一个数量级;-Encoding UTF8是必须的,因为不少随书源码的.vbproj存的是带 BOM 的 UTF-8,PowerShell 在旧版本里默认按 ANSI 读,中文注释会变乱码进而影响正则匹配。三个正则分别抓框架版本、输出类型和启动对象,抓不到就返回空字符串,这本身就是有用的信息——Target为空说明这个项目文件格式更老,可能根本不是 MSBuild 格式。

拿到这张表之后,策略就很清楚了:Target全是v4.5的,装一个 4.5 Developer Pack 一次性解决;混着v4.0和v4.5的,按章分组,先把 4.5 的那批跑通,4.0 的单独处理;Output是Library的直接跳过不跑,只读代码。

3.3 逐章验证的推荐顺序

不要从第 1 章顺着往下跑,那样会在最枯燥的控制台示例上耗光耐心,而且验证不出环境问题。我推荐的顺序是先易后难、先纯后杂:

验证阶段挑什么项目验证目标失败通常说明
第一轮OutputType=Exe的控制台项目编译器与框架版本是否匹配targeting pack 缺失或版本不符
第二轮单窗体WinExe项目设计器能否正常加载、资源是否完整.resx损坏或 DPI 缩放问题
第三轮多窗体 + 自定义控件的项目项目间引用、命名空间是否正确引用路径断链
第四轮带.config的数据示例连接字符串、数据库引擎是否可用LocalDB 实例名或 mdf 版本不符
第五轮部署/安装类示例是否有额外依赖组件第三方组件未安装

第一轮只要有一个控制台项目能输出结果,就说明工具链通了,剩下的都是项目级问题而不是环境级问题。这个判断很重要,它决定了你后面排查的方向——环境问题看框架和路径,项目问题看引用和资源。

第二轮的重点不是程序能不能弹窗,而是设计器能不能打开。设计器打不开意味着.designer.vb和.resx的配对关系断了,这时候强行 F5 运行,窗体上的控件会全部消失或者位置错乱。判断方法很简单:在解决方案资源管理器里右键窗体文件选“查看设计器”,如果报“设计器加载失败”,先别管代码,去看.resx文件能不能正常打开。

4. 把源码当语法索引:类型、事件与查询的落点

4.1 从数组到泛型集合:类型安全的收益在哪

教材前几章会先用固定长度数组讲循环,再引出集合。这个顺序在今天看有点绕,但随书源码里两类写法是并存的,正好可以对照着理解为什么后面的章节全面转向List(Of T):

' 老写法:Object 数组什么都能装,取出来必须显式转换 Dim raw(2) As Object raw(0) = 78 raw(1) = "缺考" ' 编译期不报错 ' Dim n As Integer = raw(1) ' 运行期才炸,InvalidCastException ' 新写法:泛型集合把类型检查提前到编译期 Dim scores As New List(Of Integer) From {78, 91, 64} scores.Add(88) scores.Sort() scores.RemoveAll(Function(s) s < 70) ' Lambda 直接当谓词用 ' 字典:键不存在是新增,键已存在是覆盖,这一点和哈希表一致 Dim index As New Dictionary(Of String, Integer) index("A同学") = 91 If index.ContainsKey("A同学") Then Console.WriteLine(index("A同学")) End If ' 对象初始化器:窗体示例里构造业务对象的标准写法 Dim stu As New Student With {.Name = "A同学", .Score = 91}

List(Of Integer)的From {78, 91, 64}是集合初始化器语法,编译器会翻译成连续三次Add调用,和手写等价但更紧凑。RemoveAll接收一个Predicate(Of Integer)委托,Function(s) s < 70是单行 Lambda,参数类型由编译器从委托签名推断,不用写Function(s As Integer)。字典的索引器语义要特别注意:读一个不存在的键会抛KeyNotFoundException,而写一个不存在的键是新增,两种操作长得一样但行为完全相反,源码里如果有直接索引读取的地方,加一层ContainsKey或改用TryGetValue更稳。

Student类在源码里通常是单独一个.vb文件,内容只有两个自动属性。这种类在 VB 2012 里可以写成一行一个属性,不需要手写私有字段和 Get/Set 块,编译器自动生成后备字段。读源码时看到Public Property Name As String就知道它等价于一个私有字段加两个访问器,不要被简写骗了以为没有封装。

4.2 Handles 子句与 AddHandler:两种挂事件的时机

VB 相比 C# 最舒服的地方就是Handles子句,设计器里双击控件自动生成的空方法,末尾自带Handles btnRun.Click。但随书源码到后面讲动态控件时,画风会突然变成AddHandler,很多人在这里断片:

' 写法一:设计期挂接,方法签名必须和事件委托匹配 Private Sub btnRun_Click(sender As Object, e As EventArgs) Handles btnRun.Click MessageBox.Show("来自 Handles 子句") End Sub ' 写法二:运行期挂接,用于动态创建的控件 Dim btn As New Button With {.Text = "动态按钮", .Left = 20, .Top = 20} AddHandler btn.Click, AddressOf btnRun_Click ' 复用同一个处理方法 Me.Controls.Add(btn) ' 用完记得摘掉,否则窗体关闭后控件仍被事件链引用 RemoveHandler btn.Click, AddressOf btnRun_Click

Handles子句在编译期就被翻译成AddHandler,两者的差别只在时机:前者在InitializeComponent之后由编译器生成的代码挂上,后者由你在任意时刻手动挂。这也是为什么动态创建的控件不能靠Handles声明——Handles btn.Click里的btn必须是一个在设计期就存在的字段,局部变量进不了这个子句。

AddressOf后面跟的方法签名必须和事件委托一致。EventHandler的签名是(Object, EventArgs),如果你的处理方法写成(sender As Button, e As EventArgs),编译期就过不去。这个限制在源码里经常被绕过——写一个通用的(sender As Object, e As EventArgs),然后在方法体里用DirectCast(sender, Button)拿回具体控件,这是老代码里非常常见的取巧手法,读的时候别觉得奇怪。

RemoveHandler在随书源码里几乎见不到,因为大部分示例是单窗体、程序退出就结束。但如果你把示例改造成多文档或者长时间运行的工具,漏掉这一句就是内存泄漏——窗体关了,控件对象因为事件链还被根引用着,GC收不掉。

4.3 LINQ 查询表达式:DataTable 不是 IEnumerable

数据访问那一章会把DataTable和 LINQ 放在一起讲,这是最容易卡住的地方,因为DataTable本身不实现IEnumerable(Of T),直接写From row In dt编译不过:

Imports System.Data Imports System.Linq ' 先补一个引用:System.Data.DataSetExtensions,否则 AsEnumerable 找不到 Dim dt As New DataTable("Scores") dt.Columns.Add("Name", GetType(String)) dt.Columns.Add("Score", GetType(Integer)) dt.Rows.Add("A同学", 91) dt.Rows.Add("B同学", 64) ' 四段式查询表达式:From / Where / Order By / Select Dim q = From row In dt.AsEnumerable() Where row.Field(Of Integer)("Score") >= 70 Order By row.Field(Of Integer)("Score") Descending Select New With { .Name = row.Field(Of String)("Name"), .Score = row.Field(Of Integer)("Score") } For Each item In q ' VB 2012 还不支持 $"..." 插值字符串,用连接符或 String.Format Console.WriteLine(item.Name & ": " & item.Score) Next

AsEnumerable()是System.Data.DataSetExtensions提供的扩展方法,作用是把DataTable包装成IEnumerable(Of DataRow),没有这个引用,整个查询表达式都无从谈起。Field(Of T)("列名")也是同一套扩展方法里的,泛型参数指定列的实际类型,比row("Score")返回Object再转换安全得多,而且能正确处理DBNull。

查询表达式里的Select New With {...}是匿名类型投影,item.Name和item.Score是编译器生成的只读属性,Option Infer打开时不需要写Dim q As IEnumerable(...),推断出来的类型虽然名字很长但完全可用。这里有个版本边界要记住:字符串插值$"{item.Name}"是 VB 2015 才有的语法,用 VS 2012 打开这份源码时不能用,网上抄来的新写法会直接编译失败。

5. 五个翻车现场:从项目不兼容到设计器崩溃的排查顺序

5.1 打开就提示“未安装目标框架”

现象:双击.sln,解决方案资源管理器里所有项目都是灰色,右键“重新生成”提示“该项目面向 .NET Framework 4.5,但本机未安装对应的开发工具”。

原因:随书源码的项目文件写死了TargetFrameworkVersion为v4.5,而这台机器只装了更高版本的运行时。运行时向下兼容,但编译需要的引用程序集(targeting pack)不会随运行时自动安装。

解决:优先装 4.5 的 Developer Pack 保持源码原样;实在装不上,就用 IDE 的项目属性页把目标框架改成v4.8,改完立刻重新生成一次,确认app.config里的supportedRuntime也被同步更新。改完不要急着跑,先看一眼输出窗口有没有程序集版本冲突的警告,有的话补bindingRedirect。

5.2 生成时报“未能写入输出文件,对路径的访问被拒绝”

现象:代码一行没改,重新生成却失败,错误指向bin\Debug\xxx.exe,提示拒绝访问或文件被占用。

原因:两个来源,一是解压出来的文件带着只读属性,MSBuild 覆盖不了;二是上一次调试的进程没退干净,exe还被占用;三是源码包里残留了旧机器编译出来的bin、obj,里面的 dll 和当前工程不匹配。

解决:先关掉所有调试会话,用任务管理器确认没有残留的xxx.vshost.exe;然后按 2.3 的脚本去掉只读属性并清空bin、obj;最后重新生成。如果还报占用,把整个工作区复制到另一个短路径下再试,压缩包的虚拟目录(比如某些解压软件的临时挂载点)会导致文件句柄一直不释放。

5.3 窗体设计器打不开,或者控件位置全乱

现象:代码能编译,F5 也能跑,但双击窗体文件时设计器报错;或者设计器打开了,所有控件挤在左上角,和书上截图完全不一样。

原因:位置错乱基本是高 DPI 缩放导致的。老项目用AutoScaleMode = Font配合 8.25 磅的Microsoft Sans Serif,在 125% 或 150% 缩放的显示器上,字体会被替换成宋体并触发重新布局,控件坐标按比例放大后互相重叠。设计器打不开则是.resx里的资源引用断了,通常是图片路径写的是绝对路径或者上一位使用者的桌面路径。

解决:在 100% 缩放的主显示器上打开设计器,先确认布局正常再改代码;如果必须在高分屏上工作,给项目加一个app.manifest声明 DPI 感知级别,让程序自己处理缩放而不是交给系统位图拉伸。.resx的问题则用文本编辑器打开,把ResXFileRef里的绝对路径改成相对路径。

5.4 找不到 LineShape、OvalShape 这些形状控件

现象:某几个章节的窗体设计器报“类型 Microsoft.VisualBasic.PowerPacks.LineShape 未定义”,工具箱里也搜不到这些控件。

原因:Microsoft.VisualBasic.PowerPacks这一套形状控件从 VS 2010 之后就不再随 IDE 一起提供了。教材里用它们讲绘图和简单动画的章节,依赖的是一个需要单独安装的组件包。

解决:装对应版本的 Visual Basic Power Packs(注意位数要和 IDE 一致),装完重启 IDE,工具箱里会出现“Visual Basic PowerPacks”选项卡。不想装组件的话,把形状控件替换成在Paint事件里用Graphics.DrawLine、Graphics.FillEllipse手绘,代码量增加不多,还顺便把 GDI+ 的绘图流程搞明白了,长期看更划算。

5.5 数据示例报“无法打开登录所请求的数据库”

现象:数据访问章节的程序一启动就抛SqlException,提示无法连接或无法附加数据库文件。

原因:连接字符串里写的是Data Source=(LocalDB)\v11.0,而本机装的是更新版本的 LocalDB,实例名已经变成MSSQLLocalDB;另一种情况是.mdf文件的内部版本号高于本机数据库引擎支持的级别,附加时直接被拒绝。

解决:先用sqllocaldb info列出本机所有实例名,把连接字符串里的实例名改成本机存在的那个。如果是版本不匹配,就不要再附加原文件了,用源码里附带的建表脚本重新建一个库,或者把.mdf复制一份后用更高版本的引擎升级。|DataDirectory|这个占位符也要确认指向正确——它默认是程序集所在目录,调试时是bin\Debug,发布后是安装目录,源码里把 mdf 放在项目根目录却没设置复制到输出目录的话,运行期根本找不到文件。

6. 把示例改造成自己的练手工程:一次可验证的最小改造

跑通不等于学会。我的做法是挑一个结构最简单的单窗体示例,做一次“带验证”的改造:把它的输入校验和查询逻辑抽出来,写成可以脱离界面单独验证的方法。这样改一次,语法、事件、数据三条线就都串起来了。

具体步骤:先在原解决方案里新增一个类库项目,把窗体里跟界面无关的计算逻辑搬进去,界面只负责收集输入和显示结果;然后把这个类库的RootNamespace改成自己的命名空间,避免和源码里的同名类型冲突。搬的时候会遇到一个典型问题——原代码把校验和赋值写在一个按钮点击事件里,搬出来之后必须改造成“输入参数 → 返回结果”的纯函数形态,这个过程本身就是对耦合的理解。

' 搬出来的业务方法:不依赖任何控件,可以直接在控制台里验证 Public Class ScoreService Public Function Validate(name As String, raw As String, ByRef score As Integer) As String If String.IsNullOrWhiteSpace(name) Then Return "姓名不能为空" If Not Integer.TryParse(raw, score) Then Return "分数必须是整数" If score < 0 OrElse score > 100 Then Return "分数超出 0-100 范围" Return String.Empty ' 空字符串代表校验通过 End Function End Class ' 脱离窗体验证:把边界值全跑一遍,比手点界面可靠 Dim svc As New ScoreService() Dim s As Integer Debug.Assert(svc.Validate("", "90", s) = "姓名不能为空") Debug.Assert(svc.Validate("A同学", "abc", s) = "分数必须是整数") Debug.Assert(svc.Validate("A同学", "101", s) = "分数超出 0-100 范围") Debug.Assert(svc.Validate("A同学", "90", s) = "" AndAlso s = 90)

ByRef score As Integer这个参数是关键,Integer.TryParse需要传出解析结果,用ByRef比返回一个元组更贴近教材风格;String.IsNullOrWhiteSpace在 .NET 4.0 之后才可用,正好对应这份源码的框架版本。四个Debug.Assert覆盖了空值、非数字、越界、正常四种情况,全部通过才说明搬过来的逻辑没走样。注意Debug.Assert只在 Debug 配置下生效,Release 下会被编译器移除,用它做开发期自检足够,不要当成正式的错误处理。

改造完成后的验证顺序也固定下来:先跑断言,再跑窗体手工点一遍,最后把类库单独编译一次确认没有对窗体项目的反向依赖。这三步走完,你对这份源码的掌握程度就超过了“照着敲一遍”。从那以后我每次拿到随书源码,都先跑一遍批量扫描脚本摸清框架版本和项目类型,再决定从哪一章切入,绝不先双击.sln——这个习惯帮我省掉了太多在“项目不兼容”上反复折腾的时间。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进