.net 理论常识

来源:互联网 发布:中国无神论知乎 编辑:程序博客网 时间:2024/06/06 15:40

目录面向对象: 2 什么是封装 2 什么是继承 4 什么是多态 5 什么是耦合度 6 基本定义 7 泛型 7 抽象类、接口、基类、派生类、继承 8 访问类型的差异 12 枚举 13 结构和类 16 函数、方法 21 重构、重写、重载 21 事件和委托 22 简单的工厂模式开发 22 三层基本结构 24 值类型和引用类型 25 附表 27 重新理解面向对象,进行新模式的思索和加深。 <编程是技术,也是艺术,虽然我达不到艺术的巅峰,但可以努力向这个方向前进> 蒋玉龙 2008年4月 进行基础知识的重温 QQ 66840199 Email:Loving-kiss@163.com 面向对象:三大特性是封装、继承和多态,而不再是简单的函数、方法个人理解:面向对象是一种思想,如何成功的将业务逻辑和界面逻辑分离,尽可能大的在维护、修改的时候改动尽可能少的代码(独立出来单独的功能块而不需要改一而动全身),降低程序的耦合度。最基础的核心,应该就是抽象,如何利用掌握的知识,将事物的数学模型归纳好,这是一种思想,而不是一种简单的方式方法。面向对象还是过程,很重要么?不重要么?呵呵~~程序并不是实现了功能、界面美观就是好程序,同样,也不是使用了封装、继承和多态的就是面向对象。 程序员应该比操作员追求更高的一个目标… 什么是封装封装 (encapsulation) 隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别. 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过 外部接口,一特定的访问权限来使用类的成员。封装的大致原则 1把尽可能多的东西藏起来.对外提供简捷的接口. 2把所有的属性藏起来. 例如,在抽象的基础上,我们可以将时钟的数据和功能封装起来,构成一个时钟类。按c++的语法,时钟类的声明如下: class Clock { public: //共有成员,外部接口 void SetTime(int NewH,int NewM,int NewS); void ShowTime(); private: //私有成员,外部无法访问 int Hour,Minute,Second; } 可以看到通过封装使一部分成员充当类与外部的接口,而将其他的成员隐蔽起来,这样就达到了对成员访问权限的合理控制,使不同类之间的相互影响减少到最低限度,进而增强数据的安全性和简化程序的编写工作。什么是继承 “继承”是面向对象软件技术当中的一个概念,例如在java语言中,java语言中不支持多重继承,是通过接口实现多重继承的功能。如果一个类A继承自另一个类B,就把这个A称为"B的子类",而把B称为"A的父类"。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。 继承是指一个对象直接使用另一对象的属性和方法。事实上,我们遇到的很多实体都有继承的含义。例如,若把汽车看成一个实体,它可以分成多个子实体,如:卡车、公共汽车等。这些子实体都具有汽车的特性,因此,汽车是它们的"父亲",而这些子实体则是汽车的"孩子"。 同类事物具有共同性,在同类事物中,每个事物又具有其特殊性。运用抽象的原则舍弃对象的特殊性,抽取其共同性,则得到一个适应于一批对象的类,这便是基类(父类),而把具有特殊性的类称为派生类(子类),派生类的对象拥有其基类的全部或部分属性与方法,称作派生类对基类的继承。什么是多态多态,是面向对象的程序设计语言最核心的特征。多态,意味着一个对象有着多重特征,可以在特定的情况下,表现不同的状态,从而对应着不同的属性和方法。从程序设计的角度而言,多态可以这样来实现(以java语言为例): public interface Parent(){ public void simpleCall(); } public class Child_A implements Parent{ public void simpleCall(){ //具体的实现细节; } } public class Child_B implements Parent{ public void simpleCall(){ //具体的实现细节; } } //当然还可以有其他的实现 然后,我们就可以看到多态所展示的特性了: Parent pa = new Child_A(); pa.simpleCall()则显然是调用Child_A的方法; Parent pa = new Child_B(); pa.simpleCall()则是在调用Child_B的方法。所以,我们对于抽象的父类或者接口给出了我们的具体实现后,pa 可以完全不用管实现的细节,只访问我们定义的方法,就可以了。事实上,这就是多态所起的作用,可以实现控制反转这在大量的J2EE轻量级框架中被用到,比如Spring的依赖注射机制。什么是耦合度蒋:实际上不能也不必追求到完美的最低耦合度,在一定范围内降低,就足矣。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分摸块的一个准则就是高内聚低耦合。 耦合度是指模块之间联系的紧密程度。耦合这个概念大家都很清楚,它的强弱直接影响软件的维护和升级。耦合的强弱叫做耦合度。我们的软件工程里面一个基本原则是高内聚,低耦合。但是没有耦合的系统是不存在的。因为各个模块要互相辅助才能完成一个真正的系统,所以现在好多说法是降低耦合,而没有那种说取消耦合的说法了。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差。降低模块间的耦合度能减少模块间的影响,防止对某一模块修改所引起的“牵一发动全身”的水波效应,保证系统设计顺利进行。 两个模块之间的耦合方式通常可分为7种,按其耦合度从低到高的次序依此为:非直接耦合、数据耦合、标记耦合、控制耦合、外部耦合、公共耦合、内容耦合。 内聚度是指内部各元素之间联系的紧密程度,模块的内聚种类通常可分为7种,按其内聚度从低到高的次序依此为:偶然内聚、逻辑内聚、瞬时内聚、过程内聚、通信内聚、顺序内聚、功能内聚。基本定义泛型泛型使您可以按照泛型操作的精确的数据类型定制方法、类、结构或接口。例如,您可以使用 Dictionary 泛型类并指定允许的键类型和允许的值类型,而不使用允许任何键类型和值类型的 Hashtable 类。泛型的优点包括提高的代码可重用性和类型安全性。(例程见附表)抽象类、接口、基类、派生类、继承首先抽象类中有抽象成员和非抽象成员(和普通类成员一样),而接口的所有成员都必须在实现接口的代码类中实现。抽象类可以有自己的字段,但是接口不能有自己的字段,构造函数,析造函数,静态成员或常量。从用途上来说,抽象类主要是提供为对象系列的基类,共享一些主要特性,使继承于一个抽象类的对象都具有一个共同的目的或者结构。(所以我们可以把抽象类看成是对对象的一个特性)。接口主要是为一批类提供一些相同,而有必须实现的任务。实现同一个接口的一批类,就会强制的实现一些必须实现的接口成员,使编写更规范。(所以我们可以把接口看成是对类的一个特性)。举一个例子来看:膝上电脑和台式电脑都可以由一个抽象类来派生,这个抽象类具有电脑的某些特性,比如内存的型号,大小,电源要求等。这些特性是和具体的对象有关,我们需要从电脑来派生出我们自己的膝上电脑和台式电脑类。但是无论是什么电脑他们都会有些相同的目的,比如编写程序,拽写文档,打游戏等。我们可以为这些派生的类指定一个接口,以便他们都能实现这些相同目的的功能,例如游戏功能IgameInterface. A、 抽象类 用MustInherit编写,不能被实例化 Public MustInherit Class WashingMachine Sub New() ' Code to instantiate the class goes here. End sub Public MustOverride Sub Wash Public MustOverride Sub Rinse (loadSize as Integer) Public MustOverride Function Spin (speed as Integer) as Long End Class 使用的时候,采用单继承模式,而接口可以同时多个共存; B、 接口接口是其他类型为确保它们支持某些操作而实现的引用类型。接口从不直接创建而且没有实际的表示形式,其他类型必须转换为接口类型。一个接口定义一个协定。实现接口的类或结构必须遵守其协定。接口可以包含方法、属性、索引器和事件作为成员。. 下面的示例显示一个包含默认属性、事件 E、方法 F 和属性 P 的接口: Interface IExample Default Property Item(index As Integer) As String Event E() Sub F(ByVal Value As Integer) Property P() As String End Interface 接口可以使用多重继承。 C、 D、 抽象类和接口的区别 1.类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类 接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。” 抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中 2.接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法; 3.一个类一次可以实现若干个接口,但是只能扩展一个父类 4.接口可以用于支持回调,而继承并不具备这个特点. 5.抽象类不能被密封。 6.抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非 虚的,当然您也可以声明为虚的。 7.(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。 8抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。 9.好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。 10.尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知. 11.如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单的方法来控制组件版本。如果创建的功能将在大范围的全异对象间使用,则使用接口。如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。访问类型的差异 Public:可从以下位置访问元素:同一项目中任意位置的代码,引用该项目的其他项目,以及由该项目生成的任何程序集 Friend:可以从同一程序集内部访问元素,而不能从程序集外部访问声明上下文中的所有代码均可以访问其元素。编译到同一程序集的其他类、结构和模块中的代码可以访问该程序集中的所有 Friend 元素 Protected:可以从同一个类内部或从该类派生的类中访问元素 Protected Friend:派生类或同一程序集内 Private: 仅可以从同一模块、类或结构内访问元素 A、 Overrides 重写旧的方法[获得原有方法只能用它] Overridable/virtual 指定属性或过程重写从基类继承的名称相同的属性或过程。 B、 Overloads 增加新的方法,会有多个同名方法产生指定属性或过程使用相同的名称重新声明一个或多个现有的属性或过程。 C、 Shadows 隐藏原有的方法指定已声明的编程元素重新声明并隐藏基类中的同名元素或重载元素集。枚举 Enum枚举是一个指定的常数,其基础类型可以是除 Char 外的任何整型。如果没有显式声明基础类型,则使用 Int32。编程语言通常提供语法来声明由一组已命名的常数和它们的值组成的枚举。 提供比较此类的实例的方法、将实例的值转换为其字符串表示形式的方法、将数字的字符串表示形式转换为此类的实例的方法和创建指定枚举和值的实例的方法。也可以将枚举视为位域。有关更多信息,请参见 FlagsAttribute。此类从 ValueType 继承,并实现 IComparable、IFormattable 和 IConvertible 接口。使用 Convert 类来进行转换,而不使用此类的 IConvertible 的显式接口成员实现。用于 FlagsAttribute 和 Enum 的准则 • 只有要对数值执行按位运算(AND、OR、XOR)时才对枚举使用 FlagsAttribute 自定义属性。 • 用 2 的幂(即 1、2、4、8 等)定义枚举常量。这意味着组合的枚举常量中的各个标志都不重叠。 • 请考虑为常用标志组合创建一个枚举常量。例如,如果有一个用于文件 I/O 操作的枚举包含枚举常量 Read = 1 和 Write = 2,请考虑创建枚举常量 ReadWrite = Read OR Write,该枚举常量将 Read 和 Write 标志组合在一起。此外,在某些情况下,可能会将用于组合标志的按位 OR 运算视为一种高级概念,在简单任务中不需要执行此操作。 • 将负数定义为标志枚举常量时应谨慎,因为很多标志位置都可能设置为 1,这可能使您的代码产生混淆并易于发生代码错误。 • 测试数值中是否已设置标志的一种简便方法为:在数值和标志枚举常量之间执行按位“与”操作,这种方法会将数值中与标志不对应的所有位都设置为零,然后测试该操作的结果是否等于该标志枚举常量。 • 将 None 用作值为零的标志枚举常量的名称。在按位 AND 运算中,不能使用 None 枚举常量测试标志,因为所得的结果始终为零。但是,您可以在数值与 None 枚举常量之间执行逻辑(不是按位)比较,以确定数值中是否已设置任何位。 如果创建的是值枚举而不是标志枚举,创建 None 枚举常量仍十分有用。原因是在默认情况下,公共语言运行库会将用于枚举的内存初始化为零。因此,如果不定义值为零的常量,则枚举在创建时将包含非法值。 如果明显存在应用程序需要表示的默认情况,请考虑使用值为零的枚举常量表示默认值。如果不存在默认情况,请考虑使用值为零的枚举常量(这意味着该情况不由任何其他枚举常量表示)。 • 不要定义只映射枚举本身状态的枚举值。例如,不要定义只标记枚举末尾的枚举常量。如果需要确定枚举的最后一个值,请显式检查该值。此外,如果枚举常量范围中的所有值都有效,还可以对第一个和最后一个枚举常量执行范围检查。 • 不要指定保留供以后使用的枚举常量。 • 在定义采用枚举常量作为值的方法或属性时,应考虑对该值进行验证。原因是即使数值未在枚举中定义,也可以将数值强制转换为枚举类型。 结构和类一.类和结构的示例比较:结构示例: public struct Person { string Name; int height; int weight public bool overWeight() { //implement something } } 类示例: public class TestTime { int hours; int minutes; int seconds; public void passtime() { //implementation of behavior } } 调用过程: public class Test { public static ovid Main { Person Myperson=new Person //声明结构 TestTime Mytime=New TestTime //声明类 } } 从上面的例子中我们能够看到,类的声明和结构的声明很类似,只是限定符后面是 struct 还是 class 的区别,而且使用时,定义新的结构和定义新的类的方法也很类似。那么类和结构的具体区别是什么呢? 二 .类和结构的差别 1. 值类型和引用类型 结构是值类型:值类型在堆栈上分配地址,任何的基类型(基本类型,不是基类)都是结构类型,例如:int 对应System.int32 结构,通过使用结构能够创建更多的值类型 类是引用类型:引用类型在堆上分配地址 堆栈的执行效率要比堆的执行效率高,可是堆栈的资源有限,不适合处理大的逻辑复杂的对象。所以结构处理作为基类型对待的小对象,而类处理某个商业逻辑 因为结构是值类型所以结构之间的赋值能够创建新的结构,而类是引用类型,类之间的赋值只是复制引用 注: 1.虽然结构和类的类型不相同,可是他们的基类型(基本类型,不是基类)都是对象(object),c#中任何类型的基类型都是object 2.虽然结构的初始化也使用了New 操作符可是结构对象依然分配在堆栈上而不是堆上,假如不使用“新建”(new),那么在初始化任何字段之前,字段将保持未赋值状态,且对象不可用 2.继承性 结构:不能从另外一个结构或类继承,本身也不能被继承,虽然结构没有明确的用sealed声明,可是结构是隐式的sealed . 类:完全可扩展的,除非显示的声明sealed 否则类能够继承其他类和接口,自身也能被继承 注:虽然结构不能被继承 可是结构能够继承接口,方法和类继承接口相同 例如:结构实现接口 interface IImage { void Paint(); } struct Picture : IImage { public void Paint() { // painting code goes here } private int x, y, z; // other struct members } 3.内部结构: 结构: 没有默认的构造函数,但是能够添加构造函数 没有析构函数 没有 abstract 和 sealed(因为不能继承) 不能有protected 修饰符 能够不使用new 初始化 在结构中初始化实例字段是错误的 类: 有默认的构造函数 有析构函数 能够使用 abstract 和 sealed 有protected 修饰符 必须使用new 初始化 三.如何选择结构还是类讨论了结构和类的相同之处和差别之后,下面讨论如何选择使用结构还是类: 1. 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些 2. 结构表示如点、矩形和颜色这样的轻量对象,例如,假如声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。 3. 在表现抽象和多级别的对象层次时,类是最好的选择 4. 大多数情况下该类型只是一些数据时,结构时最好的选择 第一,对于结构,不能声明一个不带参数的构造函数 不能自己声明一个结构的不带参数的构造函数,原因是编译器自动生成了一个。在一个类中,如果自己不写构造函数,那么编译器会自动生成一个默认的不带参数的构造函数。如果Time是类的话,那么它会正常编译。但是因为Time是一个结构,所以它会编译失败。 与类相同,编译器自动给结构生成的这个默认的构造函数,将结构中的变量(fields)默认值都设置成0、false、或者null。因此,应当确保默认构造函数建立的结构值再行为上有逻辑而且默认值有意义。假如不想使用默认的值,可以通过写一个非默认的构造函数,即带参数的构造函数,将参数初始化为其他值。但是需要注意的是,如果写了一个带参数的构造函数,就必须自己初始化所有的参数,编译器不会再为你初始化任何构造函数里的参数。 第二,在一个类里,可以在变量声明的时候就初始化。但是,在结构中这样做是不行的。这样做破坏了每一个结构只能在其构造函数中初始化它的变量的规则。函数、方法 Function / Sub 基本常识重构、重写、重载重构:为了提高运行效率,经常要进行程序模块化,相同的模块可以反复使用重写:方法的重写一定在不同的两个类里面,而且必须写在派生类里面重载:方法的重载有基于不同数量的参数方法的重载,还有基于不同类型的参数方法的重载,方法的重载是在同一个类里面。事件和委托蒋:个人感觉,委托实际上就相当于一个事件的指针,用来传递、引用事件的一个东西。委托是可保存对方法的引用的类。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。这样,委托就等效于一个类型安全函数指针或一个回调。虽然委托具有许多其他的用途,但这里只讨论委托的事件处理功能。一个委托声明足以定义一个委托类简单的工厂模式开发 Operation oper; oper = OperationFactory.createOperate("+"); 引发运算工厂类 oper.NumberA = 1; oper.NumberB = 2; double result = oper.GetResult(); 返回结果 /**//// /// 运算类工厂 /// class OperationFactory { public static Operation createOperate(string operate) { Operation oper = null; switch (operate) { case "+": { oper = new OperationAdd(); break; } case "-": { oper = new OperationSub(); break; } case "*": { oper = new OperationMul(); break; } case "/": { oper = new OperationDiv(); break; } } return oper; } } 大体分成三部分界面逻辑->运算工厂->不同的运算类详见:戏说面向对象程序设计C#版.pdf 三层基本结构蒋:以往一直认为三层为界面层、应用层、数据层,原来这是一个很大的误区。实际上DBServer-WebServer-Client是物理意思上的三层架构,和程序的三层架构没有什么关系真正的三层是表现层(简称UI层)、业务逻辑层(Business Logic Layer,简称BLL层)和数据访问数据访问层(Data Access Layer,简称DAL层) 值类型和引用类型值类型:简单类型、结构类型、枚举类型引用类型:类、代表、数组、接口、String 值类型: 单元直接存放“有效值” 如: int a=3; 则a内存单元就放的是3 引用类型: 单元放的是另外一个对象的引用(地址) 如: Form form1=new Form(); 就是说,在内存中开辟了一个对象new Form(),form1内存单元存放的是那个对象的地址,并非对象本身其中String 是具有值类型特点的引用类型; string是引用类型,但却与其他引用类型有着一点差别。可以从以下两个方面来看: (1)String类继承自object类。而不是System.ValueType。 (2)string本质上是一个char[],而Array是引用类型,同样是在托管的堆中分配内存。 但String作为参数传递时,却有值类型的特点,当传一个string类型的变量进入方法内部进行处理后,当离开方法后此变量的值并不会改变。原因是每次修改string的值时,都会创建一个新的对象。 区别: A值类型和引用类型内存分配值类型在栈上分配内存,而引用类型在托管堆上分配内存,却只是一种笼统的说法。更详细准确地描述是: 1、对于值类型的实例,如果做为方法中的局部变量,则被创建在线程栈上;如果该实例做为类型的成员,则作为类型成员的一部分,连同其他类型字段存放在托管堆上, 2、引用类型的实例创建在托管堆上,如果其字节小于85000byte,则直接创建在托管堆上,否则创建在LOH(Large Objet Heal)上。 B值类型和引用类型在传递参数时的影响 C装箱和拆箱 装箱是将一个值类型转换为一个对象类型(object),而拆箱则是将一个对象类型显式转换为一个值类型。对于装箱而言,它是将被装箱的值类型复制一个副本来转换,而对于拆箱而言,需要注意类型的兼容性,比如,不能将一个值为“a”的object类型转换为int的类型。 附表 A、 泛型使用 Public Class simpleList(Of itemType) Private items() As itemType Private top As Integer Private nextp As Integer Public Sub New() Me.New(9) End Sub Public Sub New(ByVal t As Integer) MyBase.New() items = New itemType(t) {} top = t nextp = 0 End Sub Public Sub add(ByVal i As itemType) insert(i, nextp) End Sub Public Sub insert(ByVal i As itemType, ByVal p As Integer) If p > nextp OrElse p < 0 Then Throw New System.ArgumentOutOfRangeException("p", _ " less than 0 or beyond next available list position") ElseIf nextp > top Then Throw New System.ArgumentException("No room to insert at ", _ "p") ElseIf p < nextp Then For j As Integer = nextp To p + 1 Step -1 items(j) = items(j - 1) Next j End If items(p) = i nextp += 1 End Sub Public Sub remove(ByVal p As Integer) If p >= nextp OrElse p < 0 Then Throw New System.ArgumentOutOfRangeException("p", _ " less than 0 or beyond last list item") ElseIf nextp = 0 Then Throw New System.ArgumentException("List empty; cannot remove ", _ "p") ElseIf p < nextp - 1 Then For j As Integer = p To nextp - 2 items(j) = items(j + 1) Next j End If nextp -= 1 End Sub Public ReadOnly Property listLength() As Integer Get Return nextp End Get End Property Public ReadOnly Property listItem(ByVal p As Integer) As itemType Get If p >= nextp OrElse p < 0 Then Throw New System.ArgumentOutOfRangeException("p", _ " less than 0 or beyond last list item") End If Return items(p) End Get End Property End Class 可以依据 simpleList 声明一个类来保存 Integer 值的列表,声明另一个类来保存 String 值的列表,并再声明一个类来保存 Date 值。除了列表成员的数据类型外,依据所有这些类创建的对象的工作方式都相同。使用代码提供给 itemType 的类型变量可以是内部类型(如 Boolean 或 Double)、结构、枚举或任何类型的类(包括应用程序定义的类)。 可以使用以下代码测试类 simpleList。 Visual Basic Public Sub useSimpleList() Dim iList As New simpleList(Of Integer)(2) Dim sList As New simpleList(Of String)(3) Dim dList As New simpleList(Of Date)(2) iList.add(10) iList.add(20) iList.add(30) sList.add("First") sList.add("extra") sList.add("Second") sList.add("Third") sList.remove(1) dList.add(#1/1/2003#) dList.add(#3/3/2003#) dList.insert(#2/2/2003#, 1) Dim s As String = _ "Simple list of 3 Integer items (reported length " _ & CStr(iList.listLength) & "):" _ & vbCrLf & CStr(iList.listItem(0)) _ & vbCrLf & CStr(iList.listItem(1)) _ & vbCrLf & CStr(iList.listItem(2)) _ & vbCrLf _ & "Simple list of 4 - 1 String items (reported length " _ & CStr(sList.listLength) & "):" _ & vbCrLf & CStr(sList.listItem(0)) _ & vbCrLf & CStr(sList.listItem(1)) _ & vbCrLf & CStr(sList.listItem(2)) _ & vbCrLf _ & "Simple list of 2 + 1 Date items (reported length " _ & CStr(dList.listLength) & "):" _ & vbCrLf & CStr(dList.listItem(0)) _ & vbCrLf & CStr(dList.listItem(1)) _ & vbCrLf & CStr(dList.listItem(2)) MsgBox(s) End Sub B、 其他