一、引言
对于ncf(net精简版的英文缩写)开发人员,应用程序选项保存一般只有两种途径选择:
1、将选项的值写入注册表,但如果所有应用程序都将值大量写入注册表的做法最终将导致注册表过大占用系统资源,而影响系统的运行效率;而且这就是很多软件在硬启设备之后不得不重新安装的原因。根据现代程序编写中"程序尽可能与系统独立"的思想,这种做法不推荐使用。
2、将选项值以一个初始化文件的方式保存,这样做可以最大限度避免系统资源占用,提高程序运行独立性,这种做法个人认为是较为可取的方案。并且这种做法在.net完整版中实现非常简单,可以直接用Xml序列化类来实现。但在专为智能移动设备定做的net精简版中,由于不提供XML序列化属性,使得保存和使用程序选项变得郁闷起来。程序开发人员不得不对每一个程序选项作写入/读取文件的编码,这个枯燥无味的步骤绝对不会是一件有趣的事情。
二、功能概述
本文中,我将利用.net的反射功能,构建一个自动完成初始化文件的保存/读写功能的类。在这个类中,只要程序作者在类内部按程序选项的名称定义好类的内部成员变量(由于这个步骤仍然需要程序员进行类内的手工编码,所以称这个类为半自动初始化类),这个类就自动将程序选项从初始化文件中保存/读取的工作,程序员不必再进行繁琐的读写文件部分的编码。而且,这个类的构建还可以有一个好处:由于应用程序的选项都以成员变量的形式保存在类的内部,程序员可以利用VS提供的自动列出变量成员的功能查询初始化文件的选项。例如这样写 string myAPPname= tobjAPPOption.General.APPName。据我所知,记住大量的程序选项的确切字符也不是什么好玩的事哦8-)
三、程序实现先决条件分析
1、初始化文件内容的需求
我们首先分析观察一个标准的windows初始化文件win.ini内容:
[windows]load=run=NullPort=Nonedevice=HP LaserJet 6L PCL,PCL5EMS3,\E5A18B631240425HPLaserJ [Desktop]Wallpaper=(无)TileWallpaper=1WallpaperStyle=0
该初始化文件的内容用方括号括住的部分我们称为初始化文件的节,每一节下都组织了一系列与节有相应功能的程序选项。如desktop节下就含有桌面墙纸(Wallpaper)/桌面墙纸铺设(WallpaperStyle)的设置。在初始化文件中程序的选项大都可以用字符串/数字这些简单的数据类型进行保存。 根据这一需求,考虑到目前在.net中使用xml文件非常方便,而且使用xml格式除可实现常规windows初始化文件的功能外,还可以多出树形结构组织的优势,所以本文设计的初始化文件确定采用xml文件格式。并作以下格式的XML文件的元素定义:
'Net对象以XML元素保存使用的格式定义'Net对象的定义'<Class ObjectName=对象名称 ObjectType(数据类型)=数据类型 > 数据内容 </数据类型>'数组的定义 目前本类中实现的数组只支持string的一维数组'<Array ObjectName=对象名称 ObjectType=数据类型 Length=数组大小> 元素定义</Array>'如果数组数组为nothing则格式如下'<Array ObjectName=对象名称 ObjectType=数据类型 Length=0>nothing</Array>'简单对象的定义'int32、String等'<SimpleObject ObjectName=对象名称 ObjectType(数据类型)=数据类型 > 数据内容 </数据类型>'当SimpleObject代表数组内的元素时,objectname代表数组的维数'ObjectName 、ObjectType、 Lenght 属性的使用举例如下'例如Redim mai32Test(7) As String'ObjectName取值为mai32Test,ObjectType取值为string[],Length 值为8
2、在.net中有一种称之为反射的功能,可以枚举特定类型对象所包含的成员变量的类型及储存值,这个功能经常被一些普通程序员忽略,认为用途并不大。但在本文中,这一功能将成为构建半自动化初始化对象的核心,我们正需要这种功能将写在初始化类中的变量类型和值自动向初始化文件保存或读取。
四、程序实现核心代码注释
1、我将这个半自动初始化文件类命名为clsAPPOption,类内结构及包含过程的功能说明如下:
两个区域#Region "应用程序使用的选项结构定义"、#Region "应用程序选项的变量声明"中的内容是按初始化选项级组织的类及类的实例,每一个类表示程序选项的一个初始化节,必须由最终使用者根据实际选项需要自行手工补充。
fnGetAppDirectory:取得应用程序的运行目录
sbInitialDefaultAPPOption:设置程序选项的默认初始值(这个过程中的代码需根据实际需要手工修改)
fnSaveAppOption:将类内的程序选项保存到一个指定的文件中. (这个过程中的部分代码需根据实际需要手工修改)
fnLoadAppOption:在指定的文件中读取应用程序的选项信息并保存到当前类中(这个过程中的部分代码需根据实际需要手工修改)
fnXMLElementToSimpleObject:将一个XMLElement转为它代表的简单对象,所谓简单对象就是诸如int32int16之类的基本net对象
fnXMLElementToClassObject:将一个XMLElement转换为它代表的类对象
fnXMLElementToArray:将一个XMLElement转为它代表的数组
fnArrayToXML:将一个数组放入XML文件中,目前只支持一维数组:
例如dim aString(10) as string
fnClassObjectToXML:将一个类对象转换为xml元素的表示形式
fnSimpleObjectToXML:将一个简单对象改为XML元素表示
注:其中fnXMLElementToXXXX和fnXXXXToXML功能相对应,互为反函数。
2、程序的实现是非常简单的,fnClassObjectToXML对指定的类进行反射操作,
使用类的类型的GetFields方法枚举类内的成员变量信息,然后根据成员变量的类型调用fnSimpleObjectToXML或fnArrayToXML,在函数结束的时候,将要转换的类以一个XMLElement对象的形式返回。
tobjClassObjectType = ni_objClassObject.GetType '取得类的类型,以利于反射调用
….其它代码
REM 以结构内的所有值进行反射取值, 并存入xml对象中
For Each tobjFieldInfo In tobjClassObjectType.GetFieldsIf tobjFieldInfo.FieldType.IsArray = False Then '只是一个简单类型,直接取得值tobjXMLElement = fnSimpleObjectToXML(tobjFieldInfo.GetValue(ni_objClassObject), _ni_objXMLDocument, _tobjFieldInfo.Name)tobjXMLClassObjectElement.AppendChild(tobjXMLElement)ElseREM 如果是一个数组类型,则进行数组方法的调用以取得值,'目前只支持一维数组元素tobjXMLElement = fnArrayToXML(tobjFieldInfo.GetValue(ni_objClassObject), ni_objXMLDocument, tobjFieldInfo.Name, tobjFieldInfo.FieldType.FullName)'将数组对象放入结构的xml对象中tobjXMLClassObjectElement.AppendChild(tobjXMLElement)End If
fnSimpleObjectToXML的实现也很简单,根据前文确立的简单对象的定义,fnSimpleObjectToXML过程所要生成的xml对象的几个要素可以这样获取:ObjectName在f nClassObjectToXML作反射后已经获取,并以参数传递的方式在调用fnSimpleObjectToXML时提供了。 ObjectType数据类型则可以利用ni_objSimpleObject.GetType.FullName方法取得, ni_objSimpleObject就是在函数调用时传入的简单对象的值。
在编写fnArrayToXML过程时出现了一点小问题,由于调用方不可能要求数组的元素逐个传入,所以对数组的元素个数以及元素的值无法在函数中以对象反射的方式直接获取。幸运的是,在Net反射操作中,可以利用Invoke方法来调用原始对象内的函数或属性过程,而数组的共享方法GetLength、GetValue功能适好是取数组元素个数及指定下标的元素值,所以问题立刻迎刃而解。
'利用反射调用数组的getLenght方法取得数组的大小,这里仅支持一维数组,但对于初始化文件已足够用了tobjXMLAttribute = ni_objXMLDocument.CreateAttribute("ArrayLength")tobjMethodInfo = tobjArrayType.GetMethod("GetLength")ReDim taobjParameter(0)taobjParameter(0) = 0ti32Tempa = tobjMethodInfo.Invoke(ni_objArray, taobjParameter)tobjXMLAttribute.Value = ti32TempatobjXMLElement.SetAttributeNode(tobjXMLAttribute)tobjXMLAttribute = Nothing'加入数组的内的元素For ti32LoopA = 0 To ti32Tempa - 1'利用反射取得数组的GetValue方法取得实际元素的值 ReDim taobjParameterType(0)taobjParameterType(0) = GetType(Integer)tobjMethodInfo = tobjArrayType.GetMethod("GetValue", taobjParameterType)ReDim taobjParameter(0)taobjParameter(0) = ti32LoopAtobjTempa = tobjMethodInfo.Invoke(ni_objArray, taobjParameter)' 将数组元素放入对xml对象中tobjXMLElementA = fnSimpleObjectToXML(tobjTempa, ni_objXMLDocument, ti32LoopA)tobjXMLElement.AppendChild(tobjXMLElementA)Next ti32LoopA
从XML转为net对象的过程基本都没有什么技术难度。我只是根据xml中包含的ObjectType信息简单获得简单对象的类型,然后直接调用net强制转换类的功能将XML文件中指定的对象储存的数值重新恢复为原来的net对象。代码如下:
'根据XMLelement结构元素中指定的对象类型,建立对象tobjType = Type.GetType(tobjXmlSimpleObjectElement.GetAttribute("ObjectType"))tobjReturnSimpleObject = Convert.ChangeType(tobjXmlSimpleObjectElement.InnerText, tobjType, Nothing)
对于Xml转为数组用数组、转为类对象的方法可以参阅本文所附源代码。
3、初始化文件类实例的调用:
类定义完成以后,可以在程序的全局范围定义初始化类的实例。
在模块内定义就可以了:
Public gobjAppOption As New clsAPPOption
可以在程序启动的时候读初始化文件信息到类中,我是放在主窗体的load事件中的。
'读取程序的选项gobjAppOption.fnLoadAppOption()
可以在程序的任意地方保存当前半自动初始化类的值到xml文件中,
我在主窗体的closed事件中加入保存代码:
gobjAppOption.fnSaveAppOption() '保存应用程序的选项
程序的任意地方你都可以调用半自动初始化文件类的实例来读取/保存程序选项的当前值:
'可以很好地利用VS提供的自动列出成员的功能列出程序选项'读取选项的示例代码MessageBox.Show(gobjAppOption.mobjAPPGeneralOption.astrShowFileFilter(0))'保存选项的示例代码'gobjAppOption.mobjAPPGeneralOption.astrHideFileFilter(0) = "*.zip"
虽然这个半自动化初始化文件类还是需要手工添加少量代码(在源代码明确注释要手工添加的部分),但还是那句老话,如果一切事情电脑都会做的时候,离程序员下岗的日子就不远了。
您可以在创建数组的同时,作为 New (Visual Basic) 子句的一部分的VB.NET初始化数组变量。也可以在后面的赋值语句中初始化数组。
数组的以下方面可以初始化:
索引上限,用于指定数组各维度的长度
数组的某些或所有元素的值
这些方面可以分开进行VB.NET初始化数组变量。但是,如果仅提供元素值而未提供上限,则由所提供的值的数目决定上限。
在创建时用 New 子句初始化数组
在 New 子句中,在圆括号中指定索引上限,并在大括号 ({}) 中提供元素值。下面的示例声明、创建并初始化一个变量以存储 Char 数据类型 (Visual Basic) 元素的数组,并指定其上限和值。
- Dim testChars As Char() = New Char(2) {"%"c, "&"c, "@"c}
执行该语句后,变量 testChars 中的数组长度为 3,其元素从索引 0 到索引 2,用于存储初始化的值。如果您同时提供了上限和值,则必须为从索引 0 到上限的每个元素都包括一个值。
注意文本类型字符 c,它将字符文本强制为 Char 数据类型。如果没有任何类型字符,双引号 (" ") 内括起来的文本默认为 String。
如果在 New 子句中提供了元素值,则不需要指定索引上限。下面的示例声明、创建并进行VB.NET初始化数组变量以存储 Boolean 数据类型 (Visual Basic) 元素的数组,并且仅指定元素值。
- Dim answers As Boolean() = New Boolean() {True, True, False, True}
执行该语句后,变量 answers 中的数组长度为 4,其元素从索引 0 到索引 3,用于存储初始化的值。
可以初始化索引上限而不初始化任何元素。如果以这种方式创建数组,必须使用后面的赋值语句来初始化每个元素值。
用后面的赋值语句的VB.NET初始化数组变量
在数组变量声明中指定索引上限。
使用一个或多个赋值语句,其中每个语句为数组中的一个元素赋一个值。下面的示例声明并创建一个变量以存储 String 数据类型 (Visual Basic) 元素的数组,并在后续语句中提供两个元素值。
- Dim comments(30) As String
- comments(0) = "This is the first comment."
- comments(5) = "This is the sixth comment."
执行这些语句后,变量 comments 中的数组长度为 31,位于索引 0 和索引 5 处的元素存储初始化的值,其他 29 个元素存储默认值。如果以这种方式初始化数组,可以初始化一些元素并跳过其他元素。
- 或 -
使用 ReDim 语句 (Visual Basic) 初始化数组的长度。
执行该语句后,变量 comments 中的数组长度为 6,其所有元素都存储默认值。
注意
可以仅在一个位置进行VB.NET初始化数组变量,索引上限。如果在数组变量名后面的括号内指定上限,则不能使用 New 子句。如果在 New 子句中的括号内指定上限,则必须将变量名后面的括号保留为空。