简介:这是一份包含150个可运行示例的VB.NET实践教程,面向初学者及需巩固基础的开发者,也适用于课堂教学与自学。教程覆盖基本语法、函数与过程、面向对象、集合数组、异常处理、文件流、控件界面、ADO.NET数据库访问、多线程、网络通信、XML处理、反射与泛型等核心模块,每个实例独立成篇,附带完整工程结构,便于直接从解决方案中打开并调试运行。解压包共1190个文件,以323个vb源程序为主,配以163个resx资源、146个vbproj工程文件、135个sln解决方案及若干aspx、css、xsd等,项目类型多样,目录分层清楚,便于按知识点检索。整个压缩包仅2.27MB,轻量易下载,已有201人学习使用。通过这150个案例,读者可对照示例理解原理并动手改写,逐步将语法知识转化为实际编码能力,尤其能强化对Windows窗体与数据库交互等典型场景的熟练度。
1. “VB.net 150例”到底是什么:从刷题误区到工具箱
拿“VB.net 150例”当标题的资料,通常不是一本从头讲到尾的教程,而是一摞按功能点切好的独立案例——文件读写一个例子、正则匹配一个例子、DataTable排序又一个例子。它的价值不在“从头刷完”,而在“按需翻找”:你接手一个老项目要改报表导出,翻目录找到那个例子;你要给WinForm加个托盘图标,翻目录找到另一个例子。150这个数字看着像刷题量,实际是覆盖面的代称。
我见过不少人的误区是打开第一例开始敲,敲到第30例就放弃。真正会用这套资料的人,是先把目录当成检索表,再按自己手头的任务挑着拉代码。这篇文章不打算复刻那150个案例,而是把这类“150例”资料背后的使用逻辑拆开:怎么分类索引、怎么挑核心案例抄作业、哪几类坑最常翻车,以及最后怎么把这堆例子沉淀成自己的模板库。
2. 按功能域拆案例:150例里最常见的八个技术分类
2.1 用目录当索引:先定位再动手
拿到“VB.net 150例”这类资源,第一步不是写代码,而是花半小时把目录过一遍,按功能域重新归类。多数案例集的目录本身是按章节排的,但章节顺序未必符合你找代码的习惯——比如把“文件”和“目录操作”拆在两个章节,实际用的时候你大概率想一起查。
我一般会把150例重新归成八个桶:文件与IO、字符串与正则、集合与泛型、数据库访问、WinForm界面组件、绘图与图像、多线程与异步、网络与JSON。归好之后,每个桶对应一个“以后遇到这类问题就翻这里”的心理锚点。这种做法不改变案例内容,只改变你调用它的方式。
拿一个实际检索场景举例:某公司内部工具要把导出的CSV按日期字段拆成多个文件,标准做法就是先定位到“文件与IO”桶,再找“按条件拆分文本文件”的案例。如果案例集里没有完全对口的,就找最接近的两个——一个教StreamReader逐行读取,一个教File.WriteAllLines,合起来就是需求答案。案例是积木,不是成品。
2.2 每个分类的命名空间与典型场景
归完桶之后,下一件事是对照每个功能域记住对应的核心命名空间。VB.NET和C#共用同一套Framework类库,所以查MSDN时不要被C#的示例代码劝退,语法翻译过来就是几句的事。150例里频率最高的几个域和命名空间映射如下:
| 功能域 | 核心命名空间 | 典型案例主题 |
|---|---|---|
| 文件与IO | System.IO | 读写文本、遍历目录、监控文件变更 |
| 字符串与正则 | System.Text、System.Text.RegularExpressions | 字符串截取、格式校验、批量替换 |
| 集合与泛型 | System.Collections.Generic | List排序、Dictionary去重、队列应用 |
| 数据库访问 | System.Data、System.Data.SqlClient(或OLEDB) | 增删改查、DataTable过滤、事务 |
| WinForm组件 | System.Windows.Forms | DataGridView绑定、菜单、托盘、拖拽 |
| 绘图与图像 | System.Drawing | 绘制图表、缩放图片、验证码生成 |
| 多线程与异步 | System.Threading、System.Threading.Tasks | 后台任务、进度条联动、线程安全 |
| 网络与JSON | System.Net、Newtonsoft.Json(或System.Text.Json) | 请求网页、JSON解析、文件上传 |
这张表不是背的,而是当你拿到一个新案例时先判断它属于哪一桶,然后直接跳到对应命名空间去查成员。时间久了你会发现,150例的表面是代码,底层其实是.NET的类库地图。
3. 三组高频案例的逐一拆解:文件、表格、界面
3.1 文件读写:StreamReader逐行处理和File类的取舍
文件读写是150例里几乎必然出现的开篇内容。最常见的案例是两种:一次性读整个文件,按行边读边处理。前者简单但吃内存,后者能处理大文件。看案例代码时重点看它选的是哪条路,以及为什么选。
' 逐行读取,适合大文件和需要边读边判断的场景 Using reader As New StreamReader("C:\data\orders.txt", Encoding.UTF8) Dim line As String Dim count As Integer = 0 While (line = reader.ReadLine()) IsNot Nothing If line.Contains("2024-") Then count += 1 End If End While End Using这段代码的关键在Using块,它保证StreamReader在跳出块时自动释放句柄。新手最容易漏掉这个,改成手动声明reader然后忘记Close,在高频读取时会把文件句柄耗尽。Encoding.UTF8参数也别省,很多老系统生成的TXT默认是ANSI编码,读入中文会乱码——这属于“玄学”问题里最常见的一种,实际一查就发现只是没指定编码。
如果你只是需要一次读入小文件做简单处理,直接用File.ReadAllLines更省事。区别是前者返回字符串数组,每行一个元素,后接File.WriteAllLines就能实现“读进来改一改写回去”的批处理。150例里这个对比通常放在相邻两个例子,读懂它们等于掌握了VB.NET文件操作的地基。
3.2 DataTable操作:过滤、排序、去重的常规套路
数据库和表格操作在150例里占比很高,因为很多业务工具就是围绕表格数据展开的。DataTable是从数据库查出来的数据在内存中的形态,案例里最常见的需求是接下来对它做筛选和排序,而不是再回数据库查一次。
' 从DataTable里过滤指定条件的行 Dim dt As DataTable = GetOrders() ' 假设已经拿到数据表 Dim filtered As DataRow() = dt.Select("OrderDate >= #2024-01-01# AND Amount > 1000") Dim sorted As DataRow() = dt.Select("", "Amount DESC") ' 去重:按客户ID取唯一值 Dim distinctTable As DataTable = dt.DefaultView.ToTable(True, {"CustomerID", "CustomerName"})dt.Select返回的是DataRow数组,不是DataTable,这是新手最容易踩的坑——后面想绑定DataGridView时发现类型对不上。第二个参数传排序表达式,排序字段多个用逗号分隔。去重那块用DefaultView.ToTable的妙处在于True表示只取每个键组合的第一行,第二行参数是去重依据的列名数组,返回的是全新DataTable,可以直接继续做数据源绑定。
这套操作的边界在于:数据量上了几十万行,内存里做Select会明显变慢,这时候就该考虑在SQL层面过滤完再取,而不是把全表拉回来。150例里通常不会讲这个性能边界,但你在实际项目里必须自己掌握。用案例代码之前先问一句:这步操作能不能在数据库端完成?能则不做内存过滤。
3.3 WinForm后台任务:不卡界面的最小写法
桌面工具几乎绕不开“点按钮开始处理”和“界面卡死”的矛盾。150例里关于多线程的内容一般有两种写法,一种是老式的BackgroundWorker,一种是新式的Task搭配async/await。区别很实际:BackgroundWorker在.NET Framework时代常见,改用.NET Core后依然兼容,但代码结构不如async/await清晰。
' 用Task避免界面卡死的标准写法 Private Async Sub BtnStart_Click(sender As Object, e As EventArgs) Handles BtnStart.Click BtnStart.Enabled = False Try Dim result As String = Await Task.Run(Function() LongRunningProcess()) TextBox1.Text = result Finally BtnStart.Enabled = True End Try End Sub Function LongRunningProcess() As String Thread.Sleep(3000) Return "处理完成" End Function注意Async Sub只能用于事件处理程序,普通函数推荐写成Async Function返回Task。Await Task.Run会把耗时操作丢到线程池,执行完自动回到UI线程更新控件,不用手动Invoke。老写法里到处是Control.CheckForIllegalCrossThreadCalls这个东西——实际是个开关,设成False可以暂时屏蔽跨线程调用的报错,但等于摁住火警铃干活,不建议用到生产代码。
这个例子是用“后台处理+UI状态切换”的套路:按钮先禁用,任务完成再启用,避免用户重复点击。如果案例集里的多线程示例只教你开线程不教你状态管理,记得自己补上这层。真实工具类软件的并发量不高,把界面弄不卡已经是及格线。
4. 数据库与LINQ:案例里被翻最多的两套写法
4.1 ADO.NET与LINQ的取舍逻辑
150例里涉及数据访问的经常是成对出现:一套是ADO.NET的Connection、Command、DataReader流程,一套是LINQ对集合和DataTable的查询语法。前者面向“直接操作数据库”,后者面向“操作内存里的数据”。很多新手分不清两者的适用边界,看到案例里有LINQ就打算用它查数据库——实际LINQ to Objects不能代替SQL连接数据库。
ADO.NET的最小骨架其实很固定:建连接、开连接、造命令、执行或取数据。这个流程最好用手写而不是拖控件,因为拖出来的TableAdapter在项目迁移时容易出兼容问题。下面这段是访问SQLite文件数据库的例子,换SqlClient就是SQL Server,换OLEDB就是Access,套路完全一致。
Dim connStr As String = "Data Source=appdata.db;Version=3;" Using conn As New SQLiteConnection(connStr) conn.Open() Dim sql As String = "SELECT Category, SUM(Amount) FROM Orders GROUP BY Category" Using cmd As New SQLiteCommand(sql, conn) Using reader As SQLiteDataReader = cmd.ExecuteReader() While reader.Read() Debug.WriteLine(reader(0).ToString() & " -> " & reader(1).ToString()) End While End Using End Using End Using三重Using嵌套是这种写法的典型形态:外层管连接,中层管命令,内层管读取器。每一层的对象都需要释放,Using块是代码量最省且不会漏的解法。reader只有向前读的能力,不能跳回上一行,这也是新手写“先判断有没有数据再读取”时容易翻车的地方——想二次遍历就先把数据存进List。
LINQ放在这段之后看更容易理解:它解决的是“数据已经拿到内存后怎么筛选排序”的问题。150例里常见的场景是把DataTable转成IEnumerable后再做复杂分组。什么时候选LINQ?当你的筛选条件是组合多个字段、需要临时分组聚合,或者要在两个集合间做连接时,LINQ的可读性明显优于手写循环。
4.2 一个SQLite + LINQ的最小组合
真实项目中很少只用到数据库或只用到LINQ,更常见的是数据库查出来,内存里再做二次加工。下面这个组合案例是我常用的模板:从订单表把近三十天的记录拉出来,然后按客户ID分组、算总额、只挑出总额超过阈值的客户。
Dim today As DateTime = DateTime.Today Dim since As DateTime = today.AddDays(-30) Dim rows As New List(Of Order)() Using conn As New SQLiteConnection(connStr) conn.Open() Using cmd As New SQLiteCommand("SELECT CustomerID, Amount, OrderDate FROM Orders WHERE OrderDate >= ?", conn) cmd.Parameters.AddWithValue("since", since) Using reader As SQLiteDataReader = cmd.ExecuteReader() While reader.Read() rows.Add(New Order() With { .CustomerID = reader("CustomerID").ToString(), .Amount = Convert.ToDecimal(reader("Amount")), .OrderDate = Convert.ToDateTime(reader("OrderDate")) }) End While End Using End Using End Using Dim result = rows _ .Where(Function(o) o.Amount > 0) _ .GroupBy(Function(o) o.CustomerID) _ .Select(Function(g) New With { .CustomerID = g.Key, .TotalAmount = g.Sum(Function(o) o.Amount) }) _ .Where(Function(x) x.TotalAmount >= 5000) _ .OrderByDescending(Function(x) x.TotalAmount) _ .ToList()这个例子的关键细节是SQL参数用问号占位,而不是拼字符串——拼SQL是150例案例集里最该跳过不学的坏习惯。AddWithValue在SQLite和SQL Server下行为略有差异,但作为通用写法够用。LINQ那段是典型的“管道式”写法:Where过滤、GroupBy分组、Select投影、OrderByDescending排序,每一步的结果都是下一步的输入。
如果你在案例集里看到整段查询全用SQL写,也在理——直接把Where、Group By写进SQL文本,再把结果读出来,完全等价。差别在于后者把逻辑留在字符串里,前者留在代码里。代码出错了调试器能定位,SQL字符串出错只能靠跑起来看。项目小怎么省事怎么来,项目大到要维护时,按我的经验,逻辑放到LINQ这段更容易改。
5. 吃透150例的避坑实战:五个高频翻车点
5.1 Option Strict关闭后的类型“隐形炸弹”
现象:从案例里复制一段代码,运行一切正常,但转到自己项目里同样的代码却报类型转换错,或者不报错但数值丢了精度。
原因:案例集为了展示简洁,常在文件头部关了Option Strict。这个选项负责强制显式类型转换,关闭后VB.NET允许Late Binding和隐式转换,看似方便,代价是运行时才暴露问题。
解决:新项目一律保持Option Strict On。遇到报错就用CType、DirectCast或.ToString()做显式转换。150例里出现的代码是演示用的,不是生产标准,抄进来之前先看文件头部有没有“Option Strict Off”,有则警惕。
5.2 DataGridView绑定List后界面不刷新
现象:给DataGridView.DataSource赋了一个List,代码里对List做Add、Remove,界面纹丝不动。
原因:List没有实现INotifyPropertyChanged和IBindingList,DataGridView监听不到集合变化事件。
解决:换用BindingList(Of T)作为数据源,或者每次操作后重新给DataSource赋一次新实例。见习程序员最喜欢用后者,因为改动最小,但界面会闪;生产代码建议用BindingList。案例里如果看到直接用List做绑定的写法,记住那只是演示最小代码,不是最佳实践。
5.3 中文乱码的编码错位
现象:读入的TXT文件里的中文全是“锟斤拷”,或者写入的CSV用Excel打开乱码。
原因:读取时用了默认编码,而文件是GBK或带BOM的UTF-8;写入CSV时用了UTF-8没有BOM,Excel默认按ANSI去猜。
解决:读取时StreamReader的构造函数显式传Encoding.Default或Encoding.GetEncoding("GB2312");写入CSV时用带BOM的UTF-8——new UTF8Encoding(True)。150例里涉及文件读写的地方前面几例多半不提编码,踩一次坑就知道这个参数比想象中更重要。
5.4 计时器和线程里改控件引发的间歇性崩溃
现象:程序跑着跑着随机弹“线程间操作无效”,有时在开发机没事,在客户机器上频繁崩溃。
原因:Timer和后台线程的回调里直接改了界面控件的属性,没走UI线程。
解决:统一用Control.BeginInvoke把更新控件属性的动作封到UI线程执行。如果是Timer事件,先确认Timer是WinForm的Timer而非Threading.Timer,后者确实跑在线程池上。很多案例只给了“在Timer里计数并显示到Label”,没有强调这个前提,实际工程里这就是崩溃源。
5.5 数据库连接字符串写死在配置文件里引发的迁移事故
现象:程序在本机能跑,复制到同事机器上报“无法连接到数据库”,查半天发现连接串里写的是本机IP或绝对路径。
原因:案例为了方便,直接在代码里拼连接串,或者把数据库路径写成相对当前目录,换机器后路径失效。
解决:连接串放App.config或web.config,数据库文件放程序目录下用相对路径,部署时用安装脚本改写配置。这个不属于代码逻辑问题,但属于案例落地时必踩的一类环境坑。150例里的连接串写法多为演示,抄的时候顺手抽到配置文件里,给你的未来减少一次“程序在我这好好的”的血泪经验。
6. 把150例改造成个人模板库:一抄二改三拆
案例资源的最大问题不是不好用,而是用完就忘。我自己的做法是给每个常用的案例做一次“模板化手术”,让它从“针对某案例场景的代码”变成“能直接填参数的架子”。手术分三步,正好对应标题里的150例这个数量该怎么消化。
第一步是“抄”。把案例代码原样跑通,确认结果符合预期。第二步是“改”,把里面写死的数值、路径、列名全部换成参数。第三步是“拆”,把案例里能独立复用的函数抽到单独的Module里。以文件拆分案例为例,原始代码可能是把某个固定文件的每一行写到固定输出文件,改完之后的模板长这样:
Public Function SplitFileByCondition( inputPath As String, outputPath As String, predicate As Func(Of String, Boolean)) As Integer Dim writeCount As Integer = 0 Using writer As New StreamWriter(outputPath, False, Encoding.UTF8) For Each line As String In File.ReadLines(inputPath, Encoding.UTF8) If predicate(line) Then writer.WriteLine(line) writeCount += 1 End If Next End Using Return writeCount End Function这个函数的意义在于它脱离了具体案例:传入不同predicate,它就能过滤出含手机号的、以日期开头的、超过指定长度的任意内容,写入不同输出文件。原来150例里的固定版本可能只满足一种场景,改造后变成可复用的工具函数,以后任何项目里需要“按条件拆文件”都能直接拖过去,改两三个参数就能用。
我自己的习惯是把改造后的模板集中放在一个独立的“代码抽屉”工程里,按功能域建文件夹,每个模板文件顶部写清楚输入输出、依赖的命名空间、常见调用方式。这个工程不是为了运行,而是为了检索——好比给150例做了第二次分类,第二次分类剔除了不适合自己业务的,保留并强化了高频使用的。遇到新项目时先翻自己的抽屉,而不是重新翻回那150个例子。
这样做还有个附带收益:你对每个案例的理解深度会和纯翻阅完全不同。抄一遍只知道“能跑”,改一遍知道“哪里能改”,拆一遍知道“哪里能抽出来”。等你能把案例里的思路移植到一个完全不同的业务场景里时,这150例才算真正入脑。我就是用这个方法消化掉手头好几份类似的案例资料,每天花一小时,两三个礼拜后自己会发现,写小工具时基本不再需要翻书——功能点已经在脑子里拆成模板了。希望帮到你。
本文还有配套的精品资源,点击获取