这段代码中inFile和line分别n是什么文献类型代码类型 它们还是变量吗?

C#中值类型和引用类型的区别深度分析
转载 & & 投稿:shichen2014
这篇文章主要介绍了C#中值类型和引用类型的区别深度分析,以通俗易懂的语言形象化的分析了C#中值类型和引用类型的区别,对于深入理解C#数据类型有着不错的参考借鉴价值,需要的朋友可以参考下
本文通俗易懂的分析了C#中值类型和引用类型的区别。分享给大家供大家参考。具体分析如下:
似乎“值类型和引用类型的区别”是今年面试的流行趋势,我已然是连续三次(目前总共也就三次)面试第一个问题就遇到这个了,这是多大的概率啊,100%,哈哈,我该买彩票去!
言归正传,咱还是先来探讨探讨这二者之间有什么区别吧。记得有一次电话面试中,我直接跟面试官说:“值类型是现金,引用类型是存折”,后来想想当时说这话虽是有点儿冲动地脱口而出,但也没什么不妥。我这人不善于背理论的教条,喜欢把书本上那些生硬的话跟现实生活中常见的事物联系起来理解和记忆。
直白点儿说:值类型就是现金,要用直接用;引用类型是存折,要用还得先去银行取现。
声明一个值类型变量,编译器会在栈上分配一个空间,这个空间对应着该值类型变量,空间里存储的就是该变量的值。引用类型的实例分配在堆上,新建一个引用类型实例,得到的变量值对应的是该实例的内存分配地址,这就像您的银行账号一样。具体哪些类型是值类型哪些是引用类型,大家翻翻书,背一背就好了,不过我想,做过一段时间的开发,即使您背不了书上教条的定义,也不会把值类型和引用类型搞混的。接下来,还是老规矩,咱看码说话吧。
代码如下:public class Person
&&& public string Name { }
&&& public int Age { }
public static class ReferenceAndValue
&&& public static void Demonstration()
&&&&&&& Person zerocool = new Person { Name = "ZeroCool", Age = 25 };
&&&&&&& Person anders = new Person { Name = "Anders", Age = 47 };
&&&&&&& int age = zerocool.A
&&&&&&& zerocool.Age = 22;
&&&&&&& Person guru =
&&&&&&& anders.Name = "Anders& Hejlsberg";
&&&&&&& Console.WriteLine("zerocool's age:\t{0}", zerocool.Age);
&&&&&&& Console.WriteLine("age's value:\t{0}", age);
&&&&&&& Console.WriteLine("anders' name:\t{0}", anders.Name);
&&&&&&& Console.WriteLine("guru' name:\t{0}", guru.Name);
上面这段代码,我们首先创建了一个Person类,包含了Name和Age两个属性,毋庸置疑,Person类是引用类型,Name也是,因为它是string类型的(但string是很特殊的引用类型,后面将专门有一篇文章来讨论),但Age则是值类型。接下来我们来看看Demonstration方法,其中演示的就是值类型跟引用类型的区别。
首先,我们声明了两个Person类的实例对象,zerocool和anders,前面提到过,这两个对象都被分配在堆上,而zerocool和anders本身其实只是对象所在内存区域的起始地址引用,换句话说就是指向这里的指针。我们声明对象实例时也顺便分别进行了初始化,首先我们看,zerocool对象的值类型成员,我们赋值为25(对,我今年25岁),anders(待会儿你们就知道是谁了)的Name属性,我们赋值为“Anders”。齐活儿,接下来看我们怎么干吧。
我们声明一个值类型变量age,直接在初始化时把zerocool的Age值赋给它,显然,age的值就是25了。但这个时候zerocool不高兴了,他想装嫩,私自把自己的年龄改成22岁,刚够法定结婚年龄。然后我们又声明了一个引用类型的guy对象,初始化时就把anders赋给它,然后anders露出庐山真面目了,他的名字叫“Anders Hejlsberg”(在此向C#之父致敬)。接下来我们来分别答应出这几个变量的值,看看有什么差别。
你可能要觉得奇怪(你要不觉得奇怪,也就不用再接着往下看了),为什么我们改了zerocool.Age的值,age没跟着变,改了anders.Name的值,guru.Name却跟着变了呢?这就是值类型和引用类型的区别。我们声明age值类型变量,并将zerocool.Age赋给它,编译器在栈上分配了一块空间,然后把zerocool.Age的值填进去,仅此而已,二者并无任何牵连,就像复印机一样,只是把zerocool.Age的值拷贝给age了。而引用类型不一样,我们在声明guy的时候把anders赋给它,前面说过,引用类型包含的是只想堆上数据区域地址的引用,其实就是把anders的引用也赋给guy了,因此这二者从此指向了同一块内存区域,既然是指向同一块区域,那么甭管谁动了里面的“奶酪”,另一个变现出来的结果也会跟着变,就像信用卡跟亲情卡一样,用亲情卡取了钱,与之关联的信用卡账上也会跟着发生变化。一提到钱,估计大家伙儿印象就深了些吧,呵呵!
另外,性能上也会有区别的。既然一个是直接操作内存,另一个则多一步先解析引用地址,那么显然很多时候值类型会减小系统性能开销。但“很多时候”不代表“所有时候”,有些时候还得量力而为,例如需要大量进行函数参数传递或返回的时候,老是这样进行字段拷贝,其实反而会降低应用程序性能。另外,如果实例会被频繁地用于Hashtable或者ArrayList之类的集合中,这些类会对其中的值类型变量进行装箱操作,这也会导致额外的内存分配和内存拷贝操作,从应用程序性能方面来看,其实也不划算。
哦对了,上面提到了一个概念,装箱。那么什么是装箱呢?其实装箱就是值类型到引用类型的转化过程。将一个值类型变量装箱成一个引用类型变量,首先会在托管堆上为新的引用类型变量分配内存空间,然后将值类型变量拷贝到托管堆上新分配的对象内存中,最后返回新分配的对象内存地址。装箱操作是可逆的,所以还有拆箱操作。拆箱操作获取指向对象中包含值类型部分的指针,然后由程序员手动将其对应的值拷贝给值类型变量。接下来我们来看看典型的装箱和拆箱操作。
代码如下:public static class BoxingAndUnboxing
&&& public static void Demonstration()
&&&&&&& int ageInt = new int();
&&&&&&& // Boxing operation.
&&&&&&& object ageObject = ageI
&&&&&&& //ageObject =
&&&&&&& // Unboxing operation.
&&&&&&& ageInt = (int)ageO
&&&&&&& Console.WriteLine(ageInt);
在该方法中,我们首先声明了一个值类型变量ageInt,但并未给它赋值,接着声明了一个典型的引用类型变量ageObject,并把ageInt赋给它,这里就进行了一次装箱操作。编译器现在托管堆上分配一块内存空间(空间大小为对象中包含的值类型变量所占空间总和外加一个方法表指针和一个SyncBlockIndex),然后把ageInt拷贝到这个空间中,再返回该空间的引用地址。接下来第13行则是拆箱操作,编译器获取到ageObject对象中值类型变量的指针,然后将其值拷贝给值类型变量。如果你把第10行注释掉的代码打开(这是通俗说法,其实就是取消注释),那么第13行就会抛出System.NullReferenceException异常,要说问什么,这又会牵扯出值类型跟引用类型另一个大的不同。看见了吧,声明ageInt时并没有赋值,如果关掉第10行代码,程序不会报错,最后打印出个0,这说明在声明值类型变量时,如果没有初始化赋值,编译器会自动将其赋值为0,既然值类型没有引用,那么它就不可能为空。引用类型不一样,它可以为空引用,一张过期作废的银行卡是可以存在。而如果将一个空的对象拆箱,编译器上哪儿去找它里面的值类型变量的指针呢?所以这也是拆箱操作需要注意的地方。
最后,我们在把值类型和引用类型之间其它一些明显区别大致罗列如下,以便大家能顺利通过面试第一问。
所有值类型都继承自System.ValueType,但是ValueType没有附加System.Object包含之外其它任何方法,不过它倒是改写了Equals和GetHashCode两个方法。引用类型变量的Equals比较的是二者的引用地址而不是内部的值,值类型变量的Equals方法比较的是二者的值而不是……哦对了,值类型压根儿没有引用地址;
值类型不能作为其它任何类型的基类型,因此不能向值类型中增加任何新的虚方法,更不该有任何抽象方法,所有的方法都是sealed的(不可重写);
未装箱的值类型分配在栈上而不是堆上,而栈又不是GC的地盘儿,因此GC根本不过问值类型变量的死活,一旦值类型变量的作用范围一过,它所占的内存空间就立即被回收掉,不劳GC亲自动手。
以上罗列的都是值类型和引用类型之间的主要区别,文码并茂,相信应该给你留下比较深刻的印象,虽不够深,但愿能起到抛砖引玉的作用。如果去面SDE职位,估计这深度就差不多了。
希望本文所述对大家的C#程序设计有所帮助。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具编写一个程序,定义三个float类型的变量,分别从键盘上输入值给它们, 然后用if else选择语句找出它们中的_百度知道
编写一个程序,定义三个float类型的变量,分别从键盘上输入值给它们, 然后用if else选择语句找出它们中的
我有更好的答案
float x, y, z,min=0; Console.WriteLine(&请输入三个float类型的值&); x = float.Parse(Console.ReadLine()); y = float.Parse(Console.ReadLine()); z = float.Parse(Console.ReadLine()); if (x & y && x & z) min = else if (y & x && y & z) min = else min = Console.WriteLine(&这三个数中的最小值为{0}&,min);
baidu://zhidao.baidu.com/question/.com/question/.html" target="_blank">http同 <a href="http://zhidao
为您推荐:
&#xe675;换一换
回答问题,赢新手礼包&#xe6b9;
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。sas数据读取详解 四种读取数据方式以及数据指针的位置 、读取mess data的两个小工具、特殊的读取技巧、infile语句及其选项(dsd dlm missover truncover obs firstobs)、proc import、自定义缺失值
时间: 23:28:16
&&&& 阅读:9513
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&(The record length is the number of characters, including spaces, in a data line.) If your data lines are long, and it looks like SAS is not reading all your data, then use the LRECL= option in the INFILE statement to specify a record length at least as long as the longest record in your data file. &
filename test ‘E:\sasData\num1.txt‘;
infile test lrecl=256;
label name=‘姓名‘ age=‘年龄‘;
input name$
1:三种读取数据的方式
1.1:list input&
变量名字符数小于32,字符型变量后加$, 两个变量间要有间隔。缺失值用 . 表示。
INPUT Name $ Age H 可行 尽量用这种,美观,易阅读
INPUT Name $Age H 可行
INPUT Name$ Age H可行&
INPUT Name$Age H 可行
适用范围?
对于日期型或其他复杂类型不适用
适用于字符(字符中不能带有空格)和数字,并以空格为分隔符的数据
列指针停留在哪?
input x $ y 1.;
/*读入结果10 1*/
1 /**读入结果10 ./ 2 /*读入结果*/
/*表明list input的指针停留在最后一个读入参数的紧挨着的空格的下一个位置*/
1.2:Column input&
适用范围?
对于对应变量值都在固定列的数据十分有用
读入的字符变量值可以带有空格
可以自由的选择读入的列,不必按顺序读入
缩减文件的存储空间,如果有空格分隔,那么空间将会增加一倍
对于缺失值的话读入空白列即可
列指针指针停留在哪?
停留在指定的列的下一列
INPUT Name $ 1-10 Age 11-13 Height 14-18;&
以上两种都只能读入标准数值类型,比如Standard numeric data contain only numerals, decimal points, minus signs, and E for scientific notation.
无法读入Numbers with embedded commas or dollar signs are examples这些非标准的数值类型。 例如100,100,1 &$100 &日期型这些数据
1.3:格式输入
w=total width & &d=number of decimal
字符格式 $informatw. & & & & & & & & &w.是标准的字符读入格式(不带informat)
数值格式 informatw.d & & & & & & & & &w.d是标准的数值读入格式(不带informat)
日期格式 informatw.
格式输入都是从第一列开始读入数据,如果对应列数不对,读入的数据也会有误
对于混合输入,其开始位置依据其他两种输入法的数据指针的位置进行读取数据
列指针指针停留在哪?
停留在规定的长度的下一格
INPUT Name $16. Age 3. +1 Type $1. +1 Date MMDDYY10.
(Score1-Score5) (4.1);
Alicia Grossman
13 c 10-28-2008 7.8 6.5 7.2 8.0 7.9
常用的可选择的格式
读入数据及其结果展示
1.4:混合输入
取长补短,中西合并!!结合三种输入法的优缺点进行数据读取
INPUT name $ 1-22 state $ year @40 size comma9.;
Yellowstone
ID/MT/WY 1872
Everglades
Great Smoky Mountains NC/TN 1926
Wolf Trap Farm
/*重点是数据指针的位置,上面的三种读入方式有描述*/
2:读取mess data
有时候,前面的四种方式都胜任不了读入的任务,那么需要更多的工具来完成相应的目标
The @&character& column pointer
sometimes you don&t know the starting column of the data, but you do know that it always comes after a particular character or word
The colon modifier :
If you only want SAS to read until it encounters a space,then you can use a colon modifier on the informat
对于字符型格式输入,如果是默认长度,则读取的数据长度小于等于8。
如果规定长度,比如$20. 则可能读取到不想要的数据
这时候可以用:
/*要读取的数据*/My dog Sam
Breed: Rottweiler
Vet Bills: $478
/*不同的读取方式和结果*/
INPUT @&Breed:& DogBreed $;
INPUT @&Breed:& DogBreed $20.;
Rottweiler Vet Bill
INPUT @&Breed:& DogBreed :$20.;
Rottweiler
3:数据读取技巧
3.1:一个input读取多行数据
如果input中的变量在一行数据中没有全部读取完,那么他会自动转到下一行读取数据
与其这样,不如我们自己明确规定他应该转到哪行去读取
INPUT City $ State $ / NormalHigh NormalLow #3 RecordHigh RecordL */表示读取下一行 #n表示读取第n行,这里的第n行应理解为一次input循环中所相对的第n行,比如第一次88 29是第3行,第二次97 65就是是3行;
Raleigh NC
/与#n的重要区别,以及与lostcard搭配的使用
lostcard的作用:Discards the first record in the group of records being read,Attempts to build an observation by beginning with the second record in the group
使用/读取多行数据时,例如input var1 / var2;var1先被读入input缓冲流中,然后/表示换行将换行后的数据也读入同一个缓冲流中,这样就意味着,必须要先读入var1才能读入var2。
而使用#n时,那么就意味着系统会开创多条input缓冲流。这样我们就能读var2再读var1;input #2var2 #1var1;
*#n与lostcard结合的实例;
input id test1 #2 idcheck test2 test3; *使用#2时会得到正确的输出结果301 304,但是使用/时只能得到一条输出,因为使用/时第三行和第四行拼凑成了一条缓冲流,下次input会直接输入第五行而不会从第四行输入;
if id ne idcheck then
put id= idcheck=;
proc print
3.2:将一行数据读取到多个观测值
双尾@@ &,当使用时告诉sas不要换行,除非数据读取完毕,或者遇到一个不以@@结尾的input,当input遇到一行结尾而还有变量没读取时会自动切换到下一行
This line-hold specifier is like a stop sign telling SAS, &Stop, hold that line of raw data.&&
data body_
input Gender $ PercentFat @@;
3.3:按条件读入需要的数据,分段读取一条数据
record理解为一横行,@的主要功能是可以在一个循环中分段读取一个record,来达到是否需要当前观测值的效果
input Team $ 13-18 @;
if Team=‘red‘;
input IdNumber 1-4 StartWeight 20-22 EndWeight 24-26;
Amelia yellow 145 124
yellow 194 177
Ashley red
yellow 220
proc print data=red_
title ‘Red Team‘;
流程详解:
1:input读取第一条记录进入缓冲流,从13-18读取数据,然后赋值给pdv中的变量Team,单尾@将缓冲流中的record位置保存下来
2:if进行判断,如果是&red&则进行下一个input语句,否则当前data循环停止,返回data步开始,将所有pdv中的变量赋为缺失值,并释放缓冲流中维持的record
3:如果是&red&则进入下一个input,继续从缓冲流当前维持的record位置读取数据,并将值赋值给pdv中的变量IdNumber, StartWeight, and EndWeight
4:返回data步开始并释放缓冲流,并清空pdv
单尾@与双尾@的异同
他们都是line-hold specifier
The trailing @ holds a line of data for subsequent INPUT statements, but releases that line of data when SAS returns to the top of the DATA step to begin building the next observation
The double trailing @ holds a line of data for subsequent INPUT statements even when SAS starts building a new observation.
简而言之是单尾抓一轮,双尾抓死不放手!!!
4:infile语句以及其中的选项
sas读取数据有很多假设,比如一个input中的变量没读取完毕、或者是一个变量读了一半另一半在下一行,sas会自动跳到下一个数据行读取数据进入input中,有时这不是我们需要的,用infile中的选项可以解决很多这种问题
firstobs=&&tells SAS at what line to begin reading data
obs= & & &&It tells SAS to stop reading when it gets to that line in the raw data file
/****************************
Ice-cream sales data for the summer
Boxes sold
Data verified by Blake White
数据集中的数据
****************************/
filename test ‘E:\sasData\num1.txt‘;
infile test firstobs=3 obs=5;
input x :$12.
Missover :MISSOVER option tells SAS that if it runs out of data, don&t go to the next data line.Instead, assign missing values to any remaining variables.
他告诉sas,说:"input没读完就别读啦,直接全部给我赋为缺失值!!!"
truncover::option tells SAS to read data for the variable until it reaches the end of the data line, or the last column specified in the format or column range, whichever comes first
上面时truncover的结果,下面是missover
根据字面意思也可以区分,trunc是截断,表明是有多少留多少!
miss是丢失,表明没有那么多就干脆为缺失吧!
dlm=‘?‘ &标明你要使用的分隔符 & &,对于常用的csv文件用dlm=‘,‘;对用tab分隔符,如果电脑编码是ASCII则用dlm=‘09‘x;如果编码为EBCDIC则用dlm=‘05‘X;
dlmstr=‘?‘ 标明你要使用的字符串分隔符
dsd(delimiter-sensitive data)这个符号做了三件事
1:忽视引号内的分隔符
2:当读取带引号的字符串时,不把引号内读作数据的一部分(比如读取"abc",读入数据集中的内容是abc)
3:将两个连续的分隔符当做缺失值处理(默认‘,‘为分隔符,要改变的话要再加dlm=选项,默认情况下sas会将多个连续分隔符当成一个处理)
5:PROC IMPORT过程。
import过程会预先扫描20行来判断变量对应的类型,并且会根据你的文件后缀来判断你的分隔符,如果是.csv则会用‘,‘,.txt则用‘09‘X,其他的需要自己声明。
会忽略引号,并将两个连续分隔符当做缺失值处理。实现dsd dlm missover的大部分功能。
PROC IMPORT DATAFILE=‘D:\truncO.csv‘ OUT=Temp REPLACE;GETNAMES=NO;RUN;&
REPLACE会将输出数据集替代原有的数据集。
GETNAMES=NO; 是不把第一行当做列名。
DATAROWS =是规定从第几行开始读取
GUESSINGROWS = 是改变默认扫面的行数
6:自己定义缺失值
missing N R;*表示将N R也默认为缺失值,当读到N R数据时,而要求是数字格式的话,那么不会将其赋值为缺失值,而会写成N R;
...................
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文:http://www.cnblogs.com/yican/p/4110956.html
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!VB编程、代码、技术问题解决方案
学长们,那里有比较好的。NET电子书(中文)。学妹谢过了
方案整理者:wjf4585 &&&&&& 发布时间:
赞助商广告
推荐解决方案
没有比较好的,中国人写的都是垃圾书
其他解决方案
Sorry,有歧义,我是说现在中国人写的VB.net书都是垃圾书
现在没发现!我也想要
等等吧,只有等等有人把国外的书翻译过来,才有好书看,不然白花钱又买不到好书,看看MSDN是最好的了。。。。。。
E文的好书挺多,中文的资料比较凌乱,但还是有的。
推荐一篇,讲VB&OOP的
VB.NET面向对象的实现
在VB&6中面向对象的能力还不是很强,但随着VB.NET的出现,其面向对象的能力大大增强。VB.Net不仅为我们提供了新的面向对象的特性,而且它也改变了我们在VB6中实现一些特性时所用的方法。在本教程中我将带你浏览一下这些特性,并将涉及到新的性能以及现有特性的变化。
本简缩教程的内容有:
 1.创建类:类关键字、类及名空间、创建方法、创建属性、重载方法等。
 2.对象的生命周期:对象的构造、对象的终止
 3.继承:实现基本的继承、阻止继承、继承与辖域、保护方法、重载方法、重载与  构造方法、创建基类以及抽象方法
 4.共享或类成员:共享方法、共享变量
 5.事件:共享事件、触发事件
 6.界面:怎样使用界面
 7.对象的处理:对象的声明等等
 8.交叉语言的继承:创建VB.NET的基类、创建C#子类、创建一个客户应用程序。
 9.可视化继承
  在VB以前的版本中创建类时,每一个类都有它自己的文件。如果VB.NET也使用这种方法的话,那VB.NET工程将是一个更大的面向对象工程因为它包含许多文件。但是幸运的是,VB.NET并不是采用这样方法来创建类。可以说这是一个创举,因为它不用为创建一个类就创建一个文件。而是在一个文件中包含许多类,这样就使得程序的可维护性更好了。&
  另外VB.NET也提供了对.NET名空间概念的支持。VB.NET用于创建属性方法的语法也有些改变。类似于Visual&C++,我们可以在类中重载这些方法。至此,我们对VB.NET的新特性已经可以略见一斑了。好吧,言归正转,现在可是为一个工程增加类。
其实,在VB.NET中增加一个类与在VB6增加一个类是很类似的。为了做到这一点我们需要先创建一个新的Winodws应用程序工程,具体操作是从菜单中选择Project(工程)-&Add&Class(增加类),这时就会弹出一个增加新项目的对话框,如图1所示。
在图1的对话框中用户可以增加任意类型的项目到工程中。在本例子中是使用缺省的项目,即增加一个类模块。不管我们选择了哪种VB&代码文件(如表单、类、模块等等),我们得到文件名字的扩展名都为.vb,如图1中的class1.vb。
这里值得指出的是,文件的类型是由它的内容决定的,而不是由文件的扩展名决定的。根据我们所选择的类型,IDE(集成开发环境)就在文件中创建不同的开始代码。
在如图1所示的对话框的最后一行给类命名为MyClass,然后点击Open键,这样一个新的文件就将增加到我们的工程中,它包含了以下简单的代码:&
Public&Class&MyClass
在一个.vb文件中可以包含多个类、模块以及其它代码。接下来的设计过程实际上跟VB的差不多,我们可以手动增加其它的代码到这个文件中去。这里值得指出的是一旦在IDE创建类的时候它就会增加一个新的文件到工程中去。
  在下面的例子中,代码包含了一个关键字End&Class。这是一个新的关键字,使用它的目的是为了在一个源文件中包含多个类,这点正是VB.NET与VB6在创建类区别的精髓所在。每当我们在VB.NET中创建类的时候,我们只是简单地将所有的的代码包含在Class和End&Class&之间。例子代码如下:&
Public&Class&TheClass&
Public&Sub&MyWorks()
End&Class&
另外在一个特定的源文件(后缀名为.vb)中,我们可以使用多个Class...End&Class块。
类与名空间
  名空间的概念是.NET环境重要内容,因为它可以提供哪个类可以被组成逻辑组的机理,并且使得这些类更容易的搜索以及管理。在VB.NET名空间是使用块结构来声明的。例如:
Namespace&MyNamespace&
Public&Class&MyClass
End&Namespace&
在Namespace...End&Namespace块之间声明的任何类、结构等等将可以使用那个名空间被寻址。在本例子中,我们的类可以使用这个名空间来引用,这样定义一个变量就变成了:&
Private&obj&As&MyNamespace.MyClass
因为名空间是使用块结构来创建的,所以在单一的源文件中就不仅可以包含多个类,而且可以包含多个名空间。
同样,在一个相同名空间的类可以被创建在分隔的文件中。换句话说,在一个VB.NET工程中,我们可以使用在不同源文件中相同的名空间,而所有在这些名空间中的类将是那个相同名空间的一部分。
为了更好地理解,下面再给出一个源文件:
Namespace&MyNamespace
Public&Class&MyClass
End&Namespace
我们在工程中还有以下一个独立的源文件,其代码如下:
Namespace&MyNamespace
Public&Class&MyOtherClass
End&Namespace
以上的两短段代码是为了说明在同一个名空间MyNamespace中有两个类:MyClass和MyOtherClass。
这里还需指出,在缺省状态下,VB.NET工程有一个根名空间(root&namespace),它实际上是工程属性的一部分。这个根名空间使用了与工程相同的名字。所以当我们使用名空间块结构的时候,我们实际上是增加到根名空间上去。因此,如果你的工程命名为MyProject,那么我们可以这样来定义一个变量:&
Private&obj&As&MyProject.MyNamespace.MyClass
当然你也可以改变根名空间,具体操作可以使用菜单选项:Project(工程)-&Properties(属性)。
  在VB.NET中方法的创建还是跟在VB6中的一样,你可以使用Sub或者Function关键字。Sub和Function的区别是:用Sub来创建一个方法,它将不返回数值;若是利用Function来创建一个方法,它将返回一个数值作为结果。例如:
Sub&MyWorks()
Function&MyValue()&As&Integer
End&Function
在VB.NET中我们仍可以使用辖域关键字,这跟在VB&6中的差不多,只是多了Protected。具体的辖域关键字有:
Private表明只能调用类中的代码;
Friend&表明可以在我们的工程/组件中调用代码;
Public&表明可以在我们的类外部调用代码;
Protected是VB.NET新增的,这个我们将在讨论继承的时候再具体阐述。
Protected&Friend&表明只能在我们的工程/组件调用代码以及我们的Subclass的代码。同样我们将在讨论继承的时候再具体阐述。
缺省地,方法的参数是声明为ByVal而不是ByRef。当然,我们仍然可以通过使用ByRef关键字来重载这个缺省的行为。
  以前我们创建属性的时候是使用Property&Get和Property&Let,但现在在VB.NET中已经将它集成到一个结构中去了。例子如下:
Private&mystrName&As&String
Public&Property&Name()&As&String
Return&mystrName
mystrName&=&Value
End&Property
缺省的属性
  在VB6中创建类的时候,我们可以为类声明一个缺省的方法或者属性。具体做法可以菜单选项:Tools(工具)-&Procedure&Attributes(过程属性)并设置Procedure&ID为缺省值。
VB.Net用两种方法改变了这种行为。第一,使用一个缺省的关键字来创建缺省的属性,使得声明更加清晰直观。但是,VB.NET还给缺省的属性引入了一种新的限制,即属性必须是一个属性阵列。&
属性阵列实际上就跟数组一样,有一个索引。在选择或者列表对象中的项目属性就是一个例子:
strText&=&MyList.Item(5)
这个项目属性没有单一的数值,而是有一组的属性,它们可以通过索引来访问。
通过使用属性阵列作为缺省属性,我们就允许程序语言避免了在使用缺省属性的多义性。正如我们在VB6中所知道的,关键字Set的限制是关键。下面再看看以下的语句:
MyValue&=&MyObject
这句是指对象MyObject还是指它的缺省属性呢?为了识别它,在VB6中使用了Set命令来处理对象,如不使用Set就是指缺省的属性。在VB.NET中这条语句是指对象,因为缺省的属性是要被索引的。为了得到缺省的属性,我们要编写以下代码:
MyValue&=&MyObject(5)
因为索引是一个清楚的指示器(我们指缺省的属性,而不是MyObject本身),所以就不会有多义的存在了。
VB.NET这样的改变就意味着属性阵列过程必须接收一个参数。例如
Private&MyMoney(100)&As&String
Default&Public&Property&Money(ByVal&Index&As&Integer)&As&String&
Money&=&MyMoney(index)
MyMoney(index)&=&Value
End&Property&
最后,看起来这些代码比VB6更清楚,但也会丢失了一些灵活性。在过去,我们喜欢使用缺省的属性。举个例子,我们在用GUI控件的时候经常使用缺省的属性,比如缺省的文本属性。
TextBox1&=&MyText&
但是这在VB.NET中已经不再有效,因为文本属性不再是一个属性阵列,相反地我们必须使用属性名字。
  VB.NET的另外一个新特性是有重载方法的能力。重载的意思是我们可以在一个类中多次声明相同名字的方法只要每一次的声明都有不同的参数列表。
不同的参数列表意味着在列表中不同类型的数据类型。现在让我们先看看以下的方法声明:
Public&Sub&MyMethod(X&As&Integer,&Y&As&Integer)
这种方法的参数列表可以看成(integer,integer)。为了重载这种方法,我们必须使用不同的参数列表,例如(integer,double)。当然你还可以改变一下数据类型的顺序,比如(integer,double)和(double,integer)是不同的,这两种也是重载。重载不能只是通过改变函数的返回类型来实现,而是要求参数的数据类型不同。
作为一个例子,假如我们想提供一个搜索的功能并且根据一些条件返回一组数据,具体代码应该为:
Public&Function&MyFindData(ByVal&Name&As&String)&As&ArrayList
(搜索数据并且返回结果)
End&Function
在VB&6中,如果我们想基于一些条件增加一个新的搜索选项,就必须增加一个不同名字的函数,也就是说VB&6还没有具备重载的能力。但是现在在VB.NET中,我们可以简单地重载已经存在的函数,这一点和Visual&C++很是相似。
Public&Overloads&Function&FindData(ByVal&Name&As&String)&As&ArrayList&
(搜索数据并且返回结果)
End&Function
Public&Overloads&Function&FindData(ByVal&Age&As&Integer)&As&ArrayList
(搜索数据并且返回结果)
End&Function&
仔细观察可以发现两种方法的声明都是有相同的方法名字。这一点在VB&6中就不行的,它要求每一个方法名字都不一样。但是,在VB.NET中就允许存在相同名字的方法,但其参数要求是不同的。值得一提的是,每一个声明都要加入Overloads关键字。
当重载一个方法的时候,我们可以使用Public、Friend等等的辖域关键字让它有不同的作用域,具体做法只要使用不同的参数列表即可。这就意味着我们可以改变MyFindData方法,使它有不同的作用域:
Public&Overloads&Function&FindData(ByVal&Name&As&String)&As&ArrayList&
(搜索数据并且返回结果)
End&Function
Friend&Overloads&Function&FindData(ByVal&Age&As&Integer)&As&ArrayList&
(搜索数据并且返回结果)
End&Function&
有了这个改变,在VB.NET工程中其它代码可以使用MyFindData。MyFindData只需要接收一个整型数据作为参数即可。
对象的生命周期
在VB&6中,对象有一个很清楚的定义以及很容易理解的生命周期的概念,对象的生命周期是由下面的事件来定义的。
Sub&Main&运行时它将作为组件被装载,并且是在对象创建之前装载。
Class_Initialize&它是在对象中其它代码运行之前运行。当对象被创建的时候它被运
行程序所调用。
Class_Terminate&是在对象中其它代码运行之后再运行。当对象被卸载的时候被运行
程序调用。
在VB.NET,对象也有生命周期的概念,但是已经跟以前大不一样了。特别地,我们不再有相同的组件级的Sub&Main(它作为一个DLL被装载)的概念,并且Class_Terminate事件也被改变了,而Class_Initialize事件被成熟的构造函数方法所取代。值得指出的是,这个构造函数方法可以接收参数。
现在在VB.NET中,我们定义一个生命周期只需要用了一个New事件,这个New事件是在对象中其它代码之前运行的,并且在对象被创建的时候被调用。
从VB&6到VB.NET确实变化很大,下面我们具体讨论。
对象构造是在我们创建一个类新的实例的时候被触发的。具体可以使用关键字NEW来实现它。
自从VB&6基于COM,创建一个对象将触发一个Sub&Main过程运行。这将发生在一个对象从一个给定的组件(通常为DLL)创建来的时候。在创建对象之前,VB&6运行程序将装载DLL(动态连接库)并运行Sub&Main过程。
.NET通用语言运行程序处理组件采取不同的方法,当然VB.NET也是这样的。这就意味着没有Sub&Main过程在组件装载时候被调用。实际上,Sub&Main只使用在当一个应用程序开始的时候。当另外的组件被应用程序装载的时候,只有在类中的代码才被调用。
其实在VB6中依靠Sub&Main是不明智的做法,因为代码将在所有错误操作之前被运行。Sub&Main中的Bugs是难以在VB6中调试。如果我们不得不使用依耐于Sub&Main概念的代码来初始化,那么我们需要在VB.NET执行一个工作区。
在每一个类中从构造函数方法中调用一个方法是很容易做到的。举个例子,我们可以在一个模块中创建一个有效的代码:
Public&Module&CentralCode
Private&blnHasRun&As&Boolean
Public&Sub&Initialize()
If&Not&blnHasRun&Then
blnHasRun&=&True
(在这里作初始化工作)
End&Module
这个程序是被设计为只运行一次,不管是怎么被调用。我们可以从类中的每一个构造函数来使用这个方法。比如&:
Public&Class&TheClass
Public&Sub&New()
CentralCode.Initialize()
(这里加入另外的工作)
以上的代码虽然作了一些额外的工作,它跟使用VB6类型的Sub&Main程序达到同样的效果。
就象Sub&Main,Class_Initialize是在其它VB6类中的代码运行之前被调用的。此外,它是在错误处理之前被调用的,所以使得调试变得很难,而错误作为一般的错误显示在客户端来实例化对象。另外地,Class_Initialize不用参数,这意味着在VB6中没有方法可以在对象被创建的时候用数据来进行初始化。&
VB.NET剔除了Class_Initialize而采用完整的构造函数方法。这个构造函数有完整的错误处理能力以及可以接收参数。所以我们可以在创建对象的时候来对它们进行初始化,这是VB.NET一个十分重要的特性。VB.NET中构造函数方法是Sub&New。
Public&Class&TheClass
Public&Sub&New()
(在这里初始化对象)
利用这种类型的构造函数,可以如下创建类的实例:
Dim&obj&As&New&TheClass()
这个例子类似于在Class_Initialize创建一个VB6代码。
但是,经常地,我们在创建对象的时候往往要用数据来初始化对象。我们可以从数据库中来装载一些数据,或者我们可以直接为对象提供数据。不管用什么方法,我们是想在对象被创建的时候为它提供一些数据。&
为了做到这点,可以增加参数列表给New方法:
Public&Class&TheClass&
Public&Sub&New(ByVal&ID&As&Integer)&
(在这里使用ID数值来初始化对象)
End&Class&
现在我们来创建类的一个实例,并且为对象提供数据,代码如下:
Dim&obj&As&New&TheClass(42)
为了增加灵活型,我们可以接收可选的参数数值。为了实现这个,可以有两种方法:通过使用Optional关键字来声明一个可选择的参数,或者通过重载New方法。为了使用Optional关键字,我们简单地声明可选择的参数,代码如下:
Public&Sub&New(Optional&ByVal&ID&As&Integer&=&-1)&
If&ID&=&-1&Then
(这里可以初始化对象)
(这里可以使用ID数值来初始化对象)
这种方法太过于理想化了,但是,既然我们不得不检查是否参数是(不是)已经提供,然后决定怎样初始化对象。New方法又两个方法可以实现。第一种是对于每种行为类型而言的,它可以通过重载来实现:
Public&Overloads&Sub&New()&
(这里可以初始化对象)
Public&Overloads&Sub&New(ByVal&ID&As&Integer)&
(这里可以使用ID数值来初始化对象)
这种方法不仅可以避免有条件的检查以及简化了代码,而且它还使得对于客户代码对象的使用都变得更清晰。这个重载New方法可以使用参数也可以不用参数,有更大的灵活性。
实际上,通过重载,我们可以创建许多不同的构造函数,也可以利用许多种不同的方法来初始化我们的对象。
在VB.NET中构造函数方法是可选的。但是只有一个例外,那就是当我们使用继承的时候,父类就只有一个构造函数需要参数。在本教程的后面我们将讨论继承。
对象的终止
在VB6中对象是在最后引用移除后被终止的。换成另外一句话说,当没有其它代码引用这个对象的时候,这个对象将自动终止。具体触发这个终止事件的是Class_Terminate。这种方法是使用引用计数来决定对象是否被终止的,是VB的一个直接的产品,它跟COM有紧密的联系。
所以我们在需要终止这个对象的使用就调用Class_Terminate事件,使得很容易控制对象。但是它也有不足之处。很明显地,虽然在两个对象之间创建循环引用是很容易,但是它们将在内存中永远地被运行。这正是在VB6中其中一种导致内存泄漏的缺陷。
这个内存泄漏问题在VB6以前的版本中是无法克服的。在VB6中,循环引用只发生在不同组件上。在VB6中,由相同的组件中的类创建而来的类将被自动终止,即使它们有循环引用。但是,如果对象来自不同的组件,循环引用问题就依然存在。这个是个很大的问题,它给许多VB开发人员带来了麻烦。所以,在VB6中程序不得不寻求各种方法来终止对象。
不象COM,.NET不是使用引用计数来决定对象是否被终止的。取而代之的是,它使用了一个有名的“垃圾收集”方案来终止对象。可能听到“垃圾收集”方案,您会云里雾里的,它的意思实际上是在VB.NET中我们不用预先定一个对象的终止方案,因此我们就不能准确地预测对象什么时候被终止的。下面我们详细探讨一下“垃圾收集”。&“垃圾收集”
在.NET中,引用计数不是一个基础功能部分。相反地,对象是通过一个“垃圾收集”机理被终止。在某特定的时间(这决定特殊的规则),一个任务会在所有的对象中运行来查找哪些已经没有被引用的对象,并且将这些对象终止,即所谓的“垃圾收集”,名字是有点土,但更形象化。
由以上的讨论我们可以知道,我们不能很准确地知道对象是在什么时候被终止的。我们除去对象的所有引用之后,并不是意味着对象快速地被终止了。此时对象还存在于内存中,直到垃圾收集处理程序运行之后才将它从内存中清除。&
垃圾收集的主要好处是它清除了由引用计数带来的循环应用问题。如果两个对象互相有引用,并且在程序中没有其它互相引用的代码时,垃圾收集程序就会发现它们并将它们终止。这一点在COM中是不可能做到的,因为它们将在内存中永远存在。
垃圾收集还有另外一个潜在的性能优点:在对象被取消引用的时候不用花很多的精力在终止对象上;利用了垃圾收集,这个终止处理过程是在应用程序处于空闲状态发生的,所以它减轻了对用户的影响。但是,垃圾收集也会发生在应用程序处在运行装载的时候,这时候系统将会运行在较低的系统资源下。
另外,我们可以通过编写代码来手动触发垃圾收集处理程序:
System.GC.Collect()
以上这个处理过程要花一些时间,但是我们在想终止对象的时候也不必每次都执行这个处理过程。我们最好是这样来设计我们的应用程序:在最后终止对象的时候才将对象从内存在清除。
Finalize方法
这个垃圾收集机理提供了一些功能,这些功能可以跟VB6中的Class_Terminate事件相媲美。当对象被终止的时候,垃圾收集处理的代码将调用Finalize方法,它就象Class_Terminate一样可以进行一些最后的内存清理工作。
Protected&Overrides&Sub&Finalize()
(此处可以进行一些内存清理工作)
以上的这些代码可以使用Protected(保护)作用域也可以使用重载关键字。这里值得指出的是,这种方法是在对象被垃圾收集机理终止之前被调用的,所以它跟Class_Terminate很是相似。
但是,我们还需要记得这种方法可以在对象被取消引用后被调用,它是通过最后一段客户代码来实现的。
实现Dispose方法
在有些场合中Finalize方法是不可接收的。如果我们有一个对象,它是使用一些非常有限的宝贵的系统资源,比如数据库连接、文件处理或者系统锁住等等。这时候我们就需要确保系统资源在对象被取消引用的时候是否被释放。&
为了实现这个目的,我们可以执行这样一个方法,它可以被客户代码调用来强迫对象被清除并且释放系统资源。虽然这不是一个很好的解决方案,但是它确实是很有效的。习惯上,这个方法就取名为Dispose,其代码如下:
Public&Sub&Dispose()
(此处可以进行一些清除工作)
在必要的时候,我们可以调用这个方法来确保内存清除工作的进行。
从上面的讨论中,我们可以深刻地体会倒VB6和VB.NET在创建类和对象的一些变化。下一个教程我们将详细讨论一下对象的继承。
本教程设计的内容有:基本的继承、阻止继承、继承与辖域、保护方法、重载方法、重载与构造方法、创建基类以及抽象方法。
当VB面向对象特性变得越来越强大的时候,我们会发觉VB缺少了继承的内容。继承是类为了得到父界面或者现有父类的一种能力。当创建一个新的从父界面或者现有父类继承而来的类的时候,我们就为原来的类创建了一个子类。这就是我们所知的父子关系。
现有许多关于继承的术语,但是很多是多余的。原来的类,即我们所继承的界面和行为可以由以下的几种互换的说法:
Parent&class&(父类)
Superclass&(超级类)
Base&class&(基类)&
而由原来的类继承而来的界面或者行为也有以下的几种互换的说法:
Child&class&(子类)
Subclass&(次类)
继承是用于生物学上的名词。比如,狗是犬科动物而犬科动物又是哺乳动物,因此作为犬科动物,狗继承了哺乳动物所有的属性和行为,这就是继承的原始涵义,即生物学上的继承。在这里借用继承这个名词用于面向对象中,具有许多相似之处。
为了在VB中满足许多程序设计人员能够象在Visual&C++中利用面向对象的功能,特别是继承的功能,VB.NET开始有了继承的概念。但是VB.NET还是不允许多重继承,即子类不能有多个父类继承而来,它只能继承一个父类。而VB.NET允许深度的继承分级结构,即一个子类可以有另外一个子类继承而来;但是VB.NET还是不允许同时继承多个父类。
在父子关系中,父对象可以拥有多个子对象,而子对象虽然都是由父对象继承而来但是它们是不同类型的。举个例子,一群子女虽然都是由父母生育的,但是他们还是有不同的相貌、性格等等。
接下来我们详细介绍VB.NET的继承。
实现基本的继承
为了探讨继承,我们先考虑以下一个商业的例子:每一个定货都有一个线路项,可能有产品线路项和服务线路项。这两种线路项是有些不同的。但是当我们在分开实现ProductLine和ServiceLine类的时候,我们就会发现其实它们有许多相同之处。如果分开编写这两个类的代码,不仅编程效率低,而且程序代码也难以维护,所以最好的方法就是使用它们一些相同的代码。
为了实现使用相同的代码,继承就起了很大的作用了。使用继承,我们可以创建一个LineItem类(父类),它包含了所有的共用代码。然后我们再创建ProductLine和ServiceLine子类,这两个类是由LineItem继承而来的。这样它们就可以自动地获得所有的共用代码了。
假如LineItem类为:
Public&Class&LineItem
Private&mintID&As&Integer
Private&mstrItem&As&String
Private&msngPrice&As&Single
Private&mintQuantity&As&Integer
Public&Property&ID()&As&Integer
Return&mintID
mintID&=&value
End&Property
Public&Property&Item()&As&String
Return&mstrItem
mstrItem&=&Value
End&Property
Public&Property&Price()&As&Single
Return&msngPrice
msngPrice&=&Value
End&Property
Public&Property&Quantity()&As&Integer
Return&mintQuantity
mintQuantity&=&Value
End&Property
Public&Function&Amount()&As&Single
Return&mintQuantity&*&msngPrice
End&Function
这个类中有所有的共用代码以及一些基本的数据区域和用于计算项目价钱的方法。
如果线路项对于产品而言,我们则需要再增加一些代码。这个项目的数值应该要验证一下确保它指一个实际的产品,并且可能的话我们也想提供产品的描述,所以ProductLine类的代码如下:
Public&Class&ProductLine
Inherits&LineItem
Private&mstrDescription&As&String
Public&ReadOnly&Property&Description()&As&String
Return&mstrDescription
End&Property
Public&Sub&New(ByVal&ProductID&As&String)
Item&=&ProductID
(此处你可以从数据库中转载产品数据)
上面语句中我们已经使用的继承语句:
Inherits&LineItem
这条语句使ProductLine类获得了LineItem类的所有的界面元素或者行为。所以我们可以如下编写客户代码:
Protected&Sub&Button1_Click(ByVal&sender&As&Object,&_
ByVal&e&As&System.EventArgs)
Dim&pl&As&ProductLine
pl&=&New&ProductLine(23abc
MessageBox.Show(pl.Item)
MessageBox.Show(pl.Description)
以上的代码使用了从LineItem类继承而来的Item属性以及ProductLine中的Description属性。
类似地,我们可以编写ServiceLine:
Public&Class&ServiceLine
Inherits&LineItem
Private&mdtDateProvided&As&Date
Public&Sub&New()
Quantity&=&1
Public&Property&DateProvided()&As&Date
Return&mdtDateProvided
mdtDateProvided&=&Value
End&Property
这里要注意的是,上面的继承语句表明ServiceLine是LineItem的子类。而DateProvided属性是从LineItem类中加入的界面。
在缺省状态下,我们创建的任何类都是作为一个基类使用的,而其它的类是有这个基类创建而来的。有时当我们想创建一个类,而这个子类不能被继承。为了做到这一点,我们可以在类的声明中使用NotInheritable关键字。
Public&NotInheritable&Class&ProductLine
当使用了这个关键字,其它代码都不能使用Inherits关键字来创建一个由这个类继承而来的子类。
继承和辖域
当我们通过继承来创建一个子类的时候,新类就可以从父类中得到了所有的Public(公共)和Friend(友)方法、属性和变量。如果你在父类中声明一个Private(私有)的方法、属性和变量,那么在新的子类中就不能得到这个类的方法、属性和变量。
当然也有个例外,就是New方法。构造函数方法必须在每一个子类中重新编写。这个内容我们在本教程的后面将深入讨论。
举个例子,我们可能从LineItem类中重写Amount方法,具体如下:
Public&Function&Amount()&As&Single&
Return&CalcAmount&
End&Function
Private&Function&CalcAmount()&As&Single
Return&fQuantity&*&fPrice
End&Function&
从这个改变中,我们可以看到公共的方法Amount使用了Private方法来工作。
当我们利用LineItem来创建子类ServiceLine的时候,任何的ServiceLine对象都有一个Amount方法,因为它在基类中声明为Public。另外一方面,这个CalcAmount方法被声明为Private,所以所有的ServiceLine类和任何的客户代码都不能访问它。
这里值得一提的是,既然Amount方法存在于LineItem类中,它就可以访问CalcAmount方法即使在ServiceLine类中不能看到这个方法。
举个例子,在我们的客户代码中我们可以这样做:
Protected&Sub&Button1_Click(ByVal&sender&As&Object,&_
ByVal&e&As&System.EventArgs)
Dim&sl&As&ServiceLine
sl&=&New&ServiceLine()
sl.Item&=&delivery&sl.Price&=&20
sl.DateProvided&=&Now
MsgBox(sl.Amount,&MsgBoxStyle.Information,&amount)
结果显示在一个消息框中,这样就阐明了CalcAmount方法被成功调用了,虽然客户代码和ServiceLine代码没有直接调用它。
Protected(保护)方法
有时,Public和Private并不能满足我们的要求。如果我们想声明一些变量或者方法为Private,那么这些变量和方法就只能在我们的类中使用;但是当我们声明一些变量和方法为Public或者Friend,那么这些变量和方法就可以在子类和客户代码中使用。但是,有时有声明一些变量和方法,让它们在子类中有效,而在客户代码中不能用,该怎么办呢?这里可以使用Protected辖域。当变量或者方法被声明为Protected的时候,对于任何类外部的代码将不能调用,但是还是可以被由它继承而来的子类调用。
举个例子吧,如下:
Public&Class&ParentClass
Protected&TheValue&As&Integer
Public&Class&SubClass
Inherits&ParentClass
Public&Function&GetValue()&As&Integer
Return&TheValue
End&Function
在上面的代码中我们有一个父类,它有一个Protected成员变量TheValue。这个变量在任何的客户代码中都不能被调用。但是这个变量对于任何的子类都是有效的因为它继承了这个父类。在这个例子中,SubClass有一个Public方法返回了Protected变量的数值,但是这个变量对于类的外部任何客户代码都是无效的。
共享或类成员
随着对象的功能越来越强大,我们有时想访问一些变量、函数或者程序而不需要一个实际的对象实例。在以前,我们可以键入这类型的代码到一个代码模块中,而不管程序是否跟一些类有关。
在VB.NET中我们有了一个较好的改变。不仅一个类可以拥有所有正常的方法和属性(这些方法和属性可以由创建类的实例来实现)而且它们可以拥有一些不需要创建类的实例的方法,这些方法就是本教程要介绍的共享方法。这些方法在其它的编程语言中也称为静态方法或者类方法。
一个共享方法不能作为一种普通的方法,通过一个对象的实例来访问,而是可以从类直接访问。下面是共享方法的一个简单的例子:
Public&Class&Math
&&Shared&Function&Add(ByVal&a&As&Integer,&ByVal&b&As&Integer)&As&Integer
Return&a&+&b
&&End&Function
我们可以不用实例化一个Math对象,就可以访问,代码如下:
Dim&result&As&Integer
result&=&Math.Add(5,&10)
现在请再仔细看看,这里我们不是使用一个变量,而是直接使用类的方法。如果用通常的方法就将导致错误,但是利用了共享方法一切都变得可以接受了。
共享方法不仅可以通过通常的方法来访问而且可以在不需要创建一个对象的条件下提供访问的功能。实际上,当一个共享方法被调用的时候,没有任何对象被创建,它就象再模块中的一个程序可以直接被调用。
共享方法和其它普通的方法一样也可以被重载,所以可以利用相同的共享方法来创建一系列变化的方法,每一种方法有不同的参数列表。
共享方法的缺省的辖域是Public。我们也可以载声明中将共享方法设置为Friend、Protected或者Private。实际上,当重载方法的时候,只要参数列表不同我们就有不同的作用域。
究竟共享方法是如何使用的呢,下面我们看看一个例子。当我们想为输入打开一个文本文件的时候,我们可以在File类中使用一个共享代码,如下:
Dim&infile&As&StreamReader&=&File.OpenText(words.txt)
Dim&strIn&As&String
str&=&infile.ReadLine()
这里没有任何的类型的文件被创建。OpenText方法是一个共享方法,它打开一个文件并且返回一个StreamReader对象。另外一个例子来自System.Guid数据类型。这个类描述了一个全局的独特的用户ID(GUID)数值,但是我们可以通过一个共享方法来创建一个新的方法。
Dim&guidID&As&Guid()
guidID&=&Guid.NewGuid()
这个NewGuid方法是直接从Guid类中调用的。它创建了一个新的Guid对象并且返回一个数值。
我们还可以创建另外一种共享成员。有时候类的所有实例需要共享一个数值,有时候每个特定类型的对象要共享相同的变量,这些时候都可以通过使用共享变量来实现目的。
一个共享变量可以使用Shared关键字来声明,这一点很象共享方法的声明:
Public&Class&MyCounter
&&Private&Shared&mintCount&As&Integer
跟共享方法一样,我们也可以根据需要来设置共享变量的作用域。在缺省状态下两者就不一样了,共享方法是Public,而共享变量是Private。
总而言之,我们要养成一个好的习惯:定义方法和变量的作用域而不使用缺省值,以避免混乱。
关于共享变量的一件重要的事情是它们在所有类的实例中都是公用。我们可以键入如下的代码来加强我们的类:
Public&Class&MyCounter
&&Private&Shared&mintCount&As&Integer
&&Public&Sub&New()
mintCount&+=&1
&&Public&ReadOnly&Property&Count()&As&Integer
Return&mintCount
&&End&Property&
End&Class&
仔细看看上面的代码:当我们为类创建了实例时,计数器就增一。+=操作符是一个在VB.NET新有的。
在任何时候我们可以通过Count属性来取出count数值。这样,如果我们运行下面的客户代码,我们可以得到结果为3。
Protected&Sub&Button4_Click(ByVal&sender&As&Object,&_
ByVal&e&As&System.EventArgs)
&&Dim&obj&As&MyCounter
&&obj&=&New&MyCounter()
&&obj&=&New&MyCounter()
&&obj&=&New&MyCounter()
&&MsgBox(obj.Count,&MsgBoxStyle.Information,counter)
如果我们再次运行这段程序,将可以得到6、9等等。只要我们的应用程序继续运行计数器就保持有效,也就是说一旦我们结束应用程序计数器就不再起作用。
这项计数对于服务器的处理过程是十分有用的,因为它可以轻松地不停地进行计数。这个数值只有在处理过程重新开始地时候才被复位。
对于共享变量另外一个通常的应用是提供了全局变量的类型。给定一个Public作用域的共享变量:&
Public&Class&TheClass
&&Public&Shared&MyGlobal&As&Integer
我们可以在客户代码中使用这个变量。
TheClass.MyGlobal&+=&5
这个变量将在我们应用程序的任何地方都是有效的,它提供了一个很好的机理来在组件、类、模块等等之间来共享数值。
在VB.NET中,继承是完全支持事件的。如果一个基类定义了一个Public事件,那么这个事件可以通过基类的代码或者任何由基类继承而来的子类所触发。
举个例子吧,我们可以先定义一个基类,代码如下:
Public&Class&Parent
&&Public&Event&ParentEvent()
&&Public&Sub&DoEvent()
RaiseEvent&ParentEvent()
很显然,类中的代码可以触发ParentEvent事件。我们还可以这样来创建一个子类:
Public&Class&SubClass
&&Inherits&Parent
&&Public&Sub&DoSomething()
RaiseEvent&ParentEvent()
这个子类是通过使用Inherits关键字继承而来的,它不仅得到了父类的属性和方法,而且继承了基类的事件。
事件可以定义任何的作用域。如果定义为Private事件,那么它就只可以通过发送对象来获得,但是Public事件就可以被任何对象访问。而Protected事件可以通过定义类或者子类来创建对象进行访问。另外Friend&事件可以被VB.NET工程中的任何对象访问。
不象方法,事件不能使用Overloads关键字来进行重载。一个类可以使用任何特定的名字来定义一个事件。因为任何子类将自动从父类重得到事件,所有Override关键字就显得没什么作用,故不能用于事件中。
事件可以定义为Shared。共享方法可以触发共享事件,而不能触发非共享事件,具体例子如下:
Public&Class&EventSource
&&Shared&Event&SharedEvent()
&&Public&Shared&Sub&DoShared()
RaiseEvent&SharedEvent()
一个共享事件也可以由共享方法或者非共享方法来触发,下面是相应的例子:
Public&Class&EventSource&
&&Public&Event&TheEvent()&
&&Shared&Event&SharedEvent()
&&Public&Sub&DoSomething()
RaiseEvent&TheEvent()
RaiseEvent&SharedEvent()
&&Public&Shared&Sub&DoShared()
RaiseEvent&SharedEvent()
&&End&Class&
如果你想从一个共享方法中触发一个非共享事件就将导致一个语法错误。
在不同工程之间触发事件
在不同工程之间完成触发事件的工作,这在VB中是不能做到的。但是在VB.NET中我们可以使用Event和RaiseEvent关键字并且使用Delegate关键字来实现它。如果你想从VB.NET工程中触发一个事件并且在另外一个工程中收到这个触发,我们就必须使用事件和Delegate的概念。
执行远程事件源
当我们仍要使用RaiseEvent语句来触发事件的时候,如果要在其它VB.NET工程中的代码接收代码我们需要采用不同的方法来声明事件。特殊地,我们需要在类外部我们要触发事件的地方通过使用Delegate语句定义事件作为delegate。
接着创建一个新的类库,将它命名为EventSource并且增加一个简单类命名为RemoteClass。
假定我们想触发一个事件,这个事件返回一个字符串参数。我们首先要利用参数的类型来声明一个Delegate:
Public&Delegate&Sub&RemoteEventHandler(ByVal&SomeString&As&String)
通常情况下VB.NET会自动创建这个Delegate。然而,有时候这个Delegate不能从其它工程中进行访问,所以我们还是对它进行显式的声明。
以下是我们创建的类,它可以触发事件:
Public&Class&RemoteClass
&&Public&Event&RemoteEvent&As&RemoteEventHandler
&&Public&Sub&DoSomething()
RaiseEvent&RemoteEvent(anyevent)
&&&这段功能是否成功的实现取决于事件本身的定义:
Public&Event&RemoteEvent&As&RemoteEventHandler
这个事件并没有显式地定义它的参数,而是依赖于Delegate来定义。反而,这个事件是定义为一个特殊的类型,即我们刚才定义Delegate的类型。
另外,用于触发事件的代码只是应用了一个简单的RaiseEvent语句:
RaiseEvent&RemoteEvent(anyevent)
这条语句提供的参数数值用于当事件被触发时的返回值。
接收远程事件
在单一的VB.NET工程中,我们可以编写代码类接收事件。具体可以增加一个Windows应用工程来解决,你可以右击它并选择Set&As&Startup&Project选项,这样它就可以在按F5的时候被运行了。
为了访问触发事件的类,我们必须给EventSource工程增加一个引用,具体操作如下:选择菜单项Project(工程)-&Add&Reference(增加引用)。
之后,我们可以增加一个按钮到窗体上,并且打开窗体的代码窗口,再引入以下的远程名空间:
Imports&System.ComponentModel
Imports&System.Drawing
Imports&System.WinForms&
Imports&EventSource&
&&&在窗体A中,我们可以使用WithEvents关键字来定义远程类。
Public&Class&FormA
&&Inherits&System.WinForms.Form
&&Private&WithEvents&objRemote&As&RemoteClass&
当objRemote项在左上角的类名字下拉表中被选择的时候,我们可以在代码窗口右上角的方法名字下拉列表中看到事件的列表。当我们选择了这个选项,以下的代码就将被创建:
Public&Sub&objRemote_RemoteEvent()&Handles&objRemote.RemoteEvent
不幸的是,这些代码是错误的,因为它没有提供我们所传递的参数。为了解决这个问题我们只需要增加参数到声明中去即可:
Public&Sub&objRemote_RemoteEvent(ByVal&Data&As&String)&_
Handles&objRemote.RemoteEvent
&&Messagebox.Show(Data)&
我们还需要增加代码来在对话框中显示结果。具体做法为:增加一个按钮到窗体中去,并为按钮增加以下的代码:
Protected&Sub&Button1_Click(ByVal&sender&As&Object,&ByVal&e&As&System.EventArgs)
&&objRemote&=&New&RemoteClass()
&&objRemote.DoSomething()
至此我们就成功完成了从一个工程中触发事件,并且在另外一个工程中接收到这些触发的事件了。
VB允许我们创建有多个界面的工程。为完成这个我们可以使用Implements关键字。在类要执行一个新界面的时候,我们需要编写代码来执行界面上的每一个方法。当继承提供了一个完美的变化后,我们可能还想在我们的对象中执行多个界面。VB.NET保存了Implements关键字,这就使得界面的概念提高了并且比起VB6来简单多了。
怎样处理界面
VB.NET进入了一个形式化的结构用于定义界面。它改变了用在类中的语法来执行界面,使得代码更加直观和清晰。
最直观的改进是引入了一个用于声明界面的正式的语法,它是使用了Interface关键字的,如下:
Public&Interface&MyInterface
&&Event&MyEvent()
&&Sub&MyMethod()
&&Function&MyFunction(ByVal&Param1&As&Integer)&As&Integer
&&Property&MyProperty()&As&String
End&Interface
&&&这种方法比起VB6的更正式。它不仅可以声明子函数、函数和属性方法,而且可以声明事件作为界面的一部分。
方法(子函数或者函数)可以使用Overloads关键字来声明。这个用于重载的准则跟前面几个教程中的教程是一样的。每一个重载声明必须有一个特别的参数列表,这依赖于参数的数据类型。
以下的例子正式使用重载方法来声明一个界面:
Public&Interface&MyInterface
&&Overloads&Sub&MyMethod()
&&Overloads&Sub&MyMethod(Data&As&String)
&&Overloads&Function&MyFunction(ByVal&Param1&As&Integer)&As&Integer
&&Overloads&Function&MyFunction(ByVal&Param1&As&Single)&As&Integer
End&Interface
当一个类使用Implement关键字来执行利用重载方法的界面的时候,类必须执行每一个重载每一个重载方法声明。
就象在VB6中,执行一个界面是通过使用Implements关键字来实现的:
Public&Class&TheClass&
&&Implements&MyInterface&
End&Class&
但是在VB6和VB.NET中还是有点区别的。在VB6中,我们执行不同的界面元素是设置为Private方法的。这些方法不直观并且容易使程序员感到困惑。而在VB.NET提供了一个清晰的、简便的语法来执行界面,它的实现是通过应用Implements关键字来完成的。
我们可以在类中简单地标记一个方法作为界面中的特殊方法的执行:
Public&Sub&MyMethod()&Implements&MyInterface.MyMethod
这样为了执行我们本例子的界面,我们必须编写以下代码:
Public&Class&TheClass
&&Implements&MyInterface
Public&Event&MyEvent()&Implements&MyInterface.MyEvent
Public&Function&MyFunction(ByVal&Param1&As&Integer)&_
As&System.Integer&Implements&OOlib.MyInterface.MyFunction
End&Function
Public&Sub&MyMethod()&Implements&OOlib.MyInterface.MyMethod
Public&Property&MyProperty()&As&String&_
Implements&OOlib.MyInterface.MyProperty
End&Property&
End&Class&
不象在VB6中,当我们执行界面的时候,我们必须执行界面中的所有元素,包括事件、方法以及属性等等。我们可以通过这个界面来创建客户代码以处理我们的对象:
Dim&obj&As&MyInterface
obj&=&New&Implementer()
obj.MyMethod
而VB6中执行界面中的一个方法需要编写如下的代码:
Private&Sub&MyInterface_MyMethod()
&&(此处是执行的代码)
执行多个界面
一个类中可以有多个执行语句,即执行多个界面。例子如下:
Public&Interface&MyInterface
&&Sub&DoSomething()
End&Interface
Public&Interface&OtherInterface
&&Sub&DoWork()
End&Interface
我们可以构造一个类来执行两个界面,代码如下:
Public&Class&TheClass
&&Implements&MyInterface
&&Implements&OtherInterface
接下来的例子,我们可以执行DoSomethind方法也可以执行DoWork方法:
Private&Sub&DoSomething()&Implements&MyInterface.DoSomething
&&(此处是执行的代码)
Private&Overloads&Sub&DoWork()&Implements&OtherInterface.DoWork
&&(此处是执行的代码)
&&&或者,如果以上两种方法是作同样的事情,我们可以利用一个方法来执行两个方法:
Private&Sub&DoSomething()&_
Implements&MyInterface.DoSomething,&OtherInterface.DoWork
(此处是执行的代码)
由上面的例子可以看出,我们可以在Implements关键字后用逗号分隔的列表类组合一系列的执行方法。
对象的处理
VB.NET在声明、构造以及执行类上比VB6有很大的变化,同样在对象的处理上也有不小的改变。这些改变影响了我们实例化对象、引用和取消引用对象以及我们使用捆绑技术的方法。下面我们就开始详细说明吧。
对象声明和实例化
VB.NET没有使用CreateObject语句来创建对象。CreateObject是VB与COM密切相关的一个产物。因为VB.NET不再使用COM,所以从VB.NET开始就不在使用CreateObject。
VB.NET使用New语句来创建对象。我们可以使用New在代码的任何地方。下面的例子我们来创建一个变量并且在类的一个实例中创建一个对象的实例:
Dim&obj&As&TheClass
obj&=&New&TheClass()
我们可以简化上面的语句:
Dim&obj&As&New&TheClass()
VB6中以上的两段语句会存在一些事情,但是在VB.NET中上面的两段语句之间是没有什么区别的,只是第二段语句缩短了而已&。
下面我们要讲讲变量的作用域。如果你在一个块结构中声明一个变量,那个变量就只有在块结构中才有效。在许多情况下,我们想在方法的作用域中声明一个变量,或者想在在块结构(比如Try...End或者loop循环结构)中创建一个实例。在这样的情况下,用实例化来组合声明有点不妥。
上面的语句为我们定义了一个变量并实例化了一个类。这条语句可能在处理继承或者多个界面的时候会更有用。我们可以声明变量为其中一种类型并且基于要执行界面的类来实例化对象:
Dim&obj&As&MyInterface&=&New&TheClass()
我们可以同时利用更复杂的语句。假如我们有个需要对象引用的方法,我们可以这样来实例化对象:
DoSomething(New&TheClass())
以上的语句是调用DoSomething方法并且传递TheClass的一个新的实例作为参数。这个新的对象就只有存在于这个方法的调用的事件内,即当方法完成之后,这个对象就自动被取消引用。
这里还得提醒一下,取消引用一个对象不是意味着马上终止这个对象。这一点我们在前面的教程中有作解释。对象只有在.NET的垃圾收集处理程序的时候才将它们从内存清除掉。
下面的例子可能更复杂。不是利用一个对象引用,我们的方法需要一个字符串。我们可以从一个方法提供一个字符串数值到我们的对象中来实例化对象并调用方法:
DoSomething(New&TheClass().GetStringData())
&&&很显然,我们需要仔细观察一下这条语句的可读性。语句的压缩往往较少了可读性,这点是我们应该注意到的。
没有Set关键字
当我们处理对象的时候,我们没有使用Set语句。在VB6&中,处理对象引用的时候我们不得不使用Set命令处理来自其它数据类型的对象。而在VB.NET中来自其它数据类型的对象是采用不同的方法来处理的,这里我们可以使用直接的参数来处理对象,就象处理整型或者字符型数据类型。这个Set命令在VB.NET中不再有效。
取消引用对象
在VB6中,我们可以通过设置对象引用为Nothing(空)来取消引用对象。这点和VB.NET中的处理方法是一样的。
Dim&obj&As&TheClass
obj&=&New&TheClass()&
obj&=&Nothing&
但是在VB.NET中这条语句有不同的效果。因为VB.NET不是使用引用计算来终止对象,而是依靠垃圾收集机理。而在VB6中,当没有变量访问对象的引用,这个对象就被终止。在VB.NET中,以下这样看法是错误的:当垃圾收集处理程序发现对象没有被引用它就被终止。正确的应该是,在最后一个引用被移除之后的一段时间后才将对象从内存中清除。
但是这并没有清除被取消对象的数值。如果你有一个长时间运行的算法,那么你最好在处理程序中显式地取消引用这个对象,这样就可以在可能的情况下由垃圾收集程序清除它们。只要我们的代码保留了对象的引用,那对象就会保持在内存中而不会被垃圾收集程序清除。
早的和晚的捆绑
VB的一个强大的功能是在处理对象的时候可以访问早的和晚的捆绑。你看完这句一定会觉得云里雾里的,好吧,下面就详细叙述一下早的和晚的捆绑吧。
所谓早的捆绑意思是说代码在直接处理对象时提前知道数据类型并且可以更有效的处理对象。早的捆绑允许IDE使用IntelliSense来提供开发效率,以及允许编译程序确保我们引用的方法存在并且提供正确的参数数值。
晚的捆绑的意思是代码动态地处理对象。这提供了更大的灵活性,因为代码没有考虑对象的类型,并且只要对象支持我们想要调用的方法,它就被处理。并且因为IDE或者编译程序不能识别对象的类型,而且没有IntelliSense和编译过程的语法检查,所以它将带来一些预想不到的灵活性。
VB.NET继承了这个传统,并且它在处理对象的时候提供了对早的和晚的捆绑的支持。
缺省状态下,所有的对象是早的捆绑。只要Option&Strict&被设置On,IDE和编译器强迫这样作,On也是缺省的。但是,如果Option&Strict&被设置Off,我们在整个代码中就使用晚的捆绑。
对象类型的使用
晚的捆绑发生在编译器不能判断对象的类型的时候。这个问题可以通过使用对象的数据类型类解决。一个数据类型对象的变量可以保留任何的数值,包括任何类型对象的引用。这样,如同下面的代码就可以在任何对象中运行以完成一个MyMethod方法,这个方法没有任何参数。
Option&Strict&Off
Module&LateBind
&&Public&Sub&DoSomething(obj&As&Object)
obj.MyMethod()
End&Module
如果对象传递这个程序(没有一个无参数的MyMethod方法),那么就会出现一个错误。这里推荐,使用晚的捆绑的代码要总是提供错误俘获:
Option&Strict&Off
Module&LateBind
&&Public&Sub&DoSomething(obj&As&Object)&
&&&obj.MyMethod()&
&&&(这里可以进行一些适当的处理来给出调用这种方法的错误)
End&Module&
虽然晚的捆绑比较灵活,但它容易导致错误并且比起早的捆绑更慢。为了产生晚的捆绑方法的调用,.NET程序必须动态地判断目标程序是否由一个方法来匹配我们调用的方法,并且它必须调用那个方法。这会比早的捆绑花更多的时间,因为早的捆绑能够提早知道方法的存在并且编译代码以使得调用变得很直接。
晚的捆绑和反射
.NET框架支持反射的概念。反射是这样一种编写代码的能力,这些代码可以检查其它.NET代码并判断它们的组成。System.Reflection名空间支持反射。
反射允许我们编写代码来检查在方法、属性和时间的类,这些类应用于其它类中。我们可以使用反射来创建这些类的实例并调用这些方法。这个处理过程很象晚的捆绑,它们都是动态的。
实际上,VB.NET使用反射来执行晚的捆绑。VB.NET不是强迫我们编写代码来使用以发现和调用方法,它是在我们使用晚的捆绑编码技术的时候来发现和调用方法。
我们可以在VB6中使用typelib&DLL(动态连接库)来执行一个反射受限的窗体。在DLL中的函数允许我们动态地发现在COM&DLL中的类和方法,并且调用它们。
CType函数的使用
使用对象数据类型来传递对象引用并在我们需要处理它们的时候将它们转换为适当的类型是十分有用的。这一切可以通过使用Ctype函数来实现,它允许我们使用类型对象的变量来使用早的捆绑方法调用:
Module&LateBind
&&Public&Sub&DoSomething(obj&As&Object)&
CType(obj,&TheClass).MyMethod()&
End&Module&
我们使用CType方法来暂时将变量转换为一个特殊的类型而不管TheClass的类型。Ctype函数是很有用的,特别是我们处理对象来执行多个界面的时候,因为我们可以引用一个对象变量并且它可以转换为适当的类型。举个例子,如果我们有一个类型的对象TheClass,这个对象执行MyInterface,那么我们可以这样来编写代码:
Dim&obj&As&TheClass
obj&=&New&TheClass
CType(obj,&MyInterface).DoSomething()
不象VB6,我们可以在VB.NET中利用早的捆绑调用对象中的其它界面而不需要声明新的界面类型的变量。
交叉语言的继承
VB.NET可以创建处理代码,这些处理代码是运行在.NET框架上的。所有的管理代码可以和其它类型的处理代码交互作用,而不管我们是用什么编程语言来创建这些组件的。这就意味着我们可以在一种编程语言上创建一个类,然后应用到另外的编程语言中,当然也包括继承。这正式交叉语言混继承的混合编程机理。
实际上,现在有许多程序涉及人员已经在应用这个技术了。许多的.NET系统类库是在C#上编写的,而我们在VB.NET上编写程序的时候,可以继承这些类作为基类。
创建VB.NET基类
举个例子,我们可以在VB.NET创建一个类库工程取名为vblib并且增加一个简单的类为Parent,代码如下:
Public&Class&Parent
Public&Sub&DoSomething()
MsgBox(Parent&DoSomething,&MsgBoxStyle.Information,&Parent)
利用这个基类我们可以在C#上创建一个子类。
创建C#子类
我们可以增加一个新类库工程,具体做法是使用菜单File(文件)-&Add&Project(增加工程)并将它命名为cslib。接着通过选择菜单Project(工程)-&Add&Reference(增加引用)来增加一个引用到vblib工程。
当我们在IDE中直接引用这个工程的时候,我们不需要VB.NET的源代码。相反地,我们可以先创建vblib工程,再创建一个组件,接着从C#工程中引用这个组件来得到访问基类的目的,代码如下:
namespace&cslib
using&System.WinF
public&class&csclass&:&Parent
public&csclass()
Messagebox.Show(csclass&constructor);
以上这个C#代码共享了VB.NET中的代码。但是,C#的语法很大程度上来自C和C++语言,所以编程会变得复杂一点。所有的代码语句的最后要以分号(;)结束,并且利用左右括号({和})来定义一个块结构。而在VB.NET中定义一个块结构是利用Sub...End&Sub语句,这正是VB.NET和C#语法上的一个差别,所以在利用VB.NET和C#混合编程的时候一定要注意到语法的差别。
我们还是仔细体会体会上面的代码吧。代码的第一行是为文件定义了名空间(namespace)。在C#中所有的名空间是显式定义在每一个代码模块中的:
namespace&cslib
在C#中的using关键字等价于在VB.NET中的Imports关键字。因为我们要使用System.WinForms和来自vblib的名空间,所以我们使用以下的语句来引入这些名空间:
using&System.WinF
代码接下去的一行是声明我们要创建的类以及表明这个类是Parent的一个子类:
public&class&csclass&:&Parent
在C#中一个子类是通过定义一个类来定义的,具体语法是类名之后加冒号(:),然后再加基类。这条语句等价于如下VB.NET的代码:
Public&Class&csclass
Inherits&Parent
在VB.NET中构造函数是通过使用保留的方法New来创建的。而在C#中构造函数是使用类名作为方法的名字来创建的,例如:
public&csclass()
Messagebox.Show(csclass&constructor);
在C#中,大括号({和})定义一个块结构,而在这个块结构中我们可以放置方法的代码。在这个例子中的方法很简单,只是显示一个对话框来指示构造函数被调用了而已。
至此我们就可以为新工程创建客户代码了。
创建客户应用程序
首先可以使用菜单File(文件)-&Add&Project(增加工程)来增加一个新的VB.NET&的Windows应用程序工程。在这个新工程可以选择菜单项Project(工程)-&Add&Reference(增加引用)来为cslib工程增加一个引用。在工程中点击鼠标右键并从弹出的菜单中选择Set&As&Startup&Project选项,这样做的目的是当你按下F5快捷键的时候工程就运行。
这里值得注意的是,vblib工程没有任何的引用,这是因为我们没有直接使用来自组件的任何代码。所有的客户应用程序所关心的是cslib工程。
当我们在IDE中直接引用cslib工程的时候,我们不需要C#代码。相反地,我们可以先创建cslib工程,再创建一个组件,接着引用来自客户工程的组件以访问我们的测试C#类。
接着我们还要增加一个按钮到窗体中,并为该按钮编写以下的代码:
Protected&Sub&Button1_Click(ByVal&sender&As&Object,&_
ByVal&e&As&System.EventArgs)
Dim&obj&As&New&cslib.csclass()
obj.DoSomething()
虽然创建一个VB.NET的子类也是没什么差别的,但是在本例子中我们是使用了不同的编程语言来实现的。其中的差别大家仔细体会,相信会对你有所帮助。
好了,当我们运行应用程序并点击按钮的时候,我们就可以看到一个对话框,对话框上面显示了我们所调用的csclass的构造函数;以及另外一个对话框,其上面显示了我们所调用的VB.NET基类的DoSomething方法。
可视化继承
上面的教程我们已经讨论了VB.NET新的面向对象的特性,其中很大一部分是在阐述继承。可能许多程序涉及人员还不知道VB.NET还支持Windows窗体的可视化继承。可视化继承,就是说我们可以创建一个Windows窗体,然后我们可以继承这个窗体使得其它窗体具有与该窗体相同的版面布置、控件和行为。
我们也可以使用继承来创建我们自己的Windows控件。比如,我们可以创建一个性能提高了的TextBox(文本框)来实现数据输入的特殊的有效性校验。具体的做法是:通过继承创建一个由原始文本框控件类继承而来的子类,并进行适当的修改以提高文本框的性能,使之可以实现对数据输入的有效性校验。
这一点跟Web表单控件是相同的,Web表单控件可以由一个已经存在的Web表单控件来创建一个子类。我们的子类可以重载已有的函数或者增加一些新的函数。
至此“VB.NET面向对象的实现”教程就全部结束,有必要在这里作番总结。VB.NET不仅给我们提供了继承,而且还提供了其它许多重要的新特性。
VB.NET改进了我们创建和处理多界面的方法,使得我们可以更容易地使用它们。另外,VB.NET支持事件作为界面的一个部分,使得我们现在可以在界面上表达所有的元素:方法、属性、事件。
此外,在VB.NET中终止对象不是通过引用计数来实现,而是利用“垃圾收集”处理程序来将对象从内存中清除。
总而言之,VB.NET比起VB来说,其面向对象的能力大大增加了,而且VB.NET也保留了低版本绝大多数的特性。
哪里有MSDN下载呢?
最新讲.NET的.
VS.Net自带的MSDN
E文版的要不要,MS的原版教材!
更多建议:1
赞助商广告

我要回帖

更多关于 经营者类型代码是什么 的文章

 

随机推荐