大家都使用过.NET的序列化机制。只要给类型标上 [Serializable] 特性,即可将任何数据对象转化为二进制数据形式,以方便保存或传输。并且使用起来非常方便。下面是一个最简单的例子:
//
写数据
using
( FileStream writer
=
new
FileStream( fileName, FileMode.Create ) )
{
IFormatter formatter
=
new
BinaryFormatter();
formatter.Serialize( writer, data );
}
//
读数据
using
( FileStream reader
=
new
FileStream( fileName, FileMode.Open ) )
{
IFormatter formatter
=
new
BinaryFormatter();
return
formatter.Deserialize( reader )
as
T;
}
用序列化机制,就不需要用户去考虑特定的数据结构,提供了一般化的数据保存方式,此功能非常强大。由于自己不满足仅仅会使用,于是就想能否自己也实现个类似的功能呢?
——于是就有了下面的XmlSave类,这个类将用户自定义的类型以XML格式保存到文件中并可以从文件中恢复对象。
先看其使用方法(下面的Student类型是自定义类型的代表,其属性不具有实际意义):
class
Student
{
[Save(
""
)]
public
string
Name {
get
;
set
; }
[Save(
""
)]
public
List
<
int
>
Number {
get
;
set
; }
}
……
//
写入
XmlSave x
=
new
XmlSave(
"
d:\\a.txt
"
);
Student s
=
new
Student();
s.Name
=
"
xbc
"
;
s.Number
=
new
List
<
int
>
();
s.Number.Add(
0
);
s.Number.Add(
1
);
x.Wirte(s);
//
读取
XmlSave x
=
new
XmlSave(
"
d:\\a.txt
"
);
Student t
=
x.Read()
as
Student;
XmlSave类使用.NET的反射机制,自动解析出类型的结构,并保存到XML文件中。
上面这个例子生成的XML文件如下:Student类型中的属性有个Save()特性,这是个自定义的特性,只所以需要这个类型是因为可以让用户决定该保存类型的哪些属性,只有加上这个标志的属性才会被保存到文件中。
<?
xml version="1.0" standalone="yes"
?>
<
root
Type
="XBC.Student"
Name
="xbc"
>
<
Number
IsList
="True"
>
<
Item
>
0
</
Item
>
<
Item
>
1
</
Item
>
</
Number
>
</
root
>
现阶段,XmlSave可以保存的类型可分为四种:
一、"int",即.NET内置的简单类型,如:System.Boolean,Byte,SByte,Char,Decimal,Double,Int32,String,Enum,Guid……
int
<?
xml version="1.0" standalone="yes"
?>
<
root
Type
="System.Int32"
>
<
Item
>
4
</
Item
></
root
>
二、"List<int>"简单类型的List<T>泛型集合
List
<?
xml version="1.0" standalone="yes"
?>
<
root
IsList
="True"
Type
="System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
>
<
Item
>
1
</
Item
>
<
Item
>
3
</
Item
>
<
Item
>
5
</
Item
>
</
root
>
三、"Student"自定义类型
Student
<?
xml version="1.0" standalone="yes"
?>
<
root
Type
="XBC.Student"
Name
="xbc"
Age
="23"
/>
四、"List<Student>"自定义类型的泛型List<T>类型
List
<?
xml version="1.0" standalone="yes"
?>
<
root
IsList
="True"
Type
="System.Collections.Generic.List`1[[XBC.Student, XmlSave, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"
>
<
Index
Name
="xbc"
Age
="23"
/>
<
Index
Name
="fvju"
Age
="20"
/>
</
root
>
并且在自定义类型时上面这四种情况可以嵌套使用。
例如下面的Student类型:
复杂的Student类型
class
Student
{
[Save(
""
)]
public
string
Name {
get
;
set
; }
List
<
StudentTime
>
_times
=
new
List
<
StudentTime
>
();
[Save( "" )]
p
ublic
List
<
StudentTime
>
Times
{
get
{
return
_times; }
set
{ _times
=
value; }
}
}
class
StudentTime
{
[Save(
""
)]
public
int
Year {
get
;
set
; }
[Save(
""
)]
public
int
Month {
get
;
set
; }
}
然后再定义List<Student> ss对象,生成的XML文件格式为:
List
<?
xml version="1.0" standalone="yes"
?>
<
root
IsList
="True"
Type
="System.Collections.Generic.List`1[[XBC.Student, XmlSave, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"
>
<
Index
Name
="xbc"
>
<
Times
IsList
="True"
Type
="XBC.StudentTime"
>
<
Index
Year
="2007"
Month
="9"
/>
<
Index
Year
="2011"
Month
="3"
/>
</
Times
>
</
Index
>
<
Index
Name
="fvju"
>
<
Times
IsList
="True"
Type
="XBC.StudentTime"
>
<
Index
Year
="2009"
Month
="4"
/>
</
Times
>
</
Index
>
</
root
>
现在已介绍了XmlSave的使用方法,下面将介绍其实现。由于实现起来比较复杂,所以本人选取几个比较重要的方面讲解,详细请见源代码。
一、保存数据
1.先判断这个数据是否为List<T>,用函数IsList判断,这个函数内部考查Type对象是否实现IList接口。
若为List<T>类型,再判断T是简单类型(int)还是复杂类型(Student),简单List<T>用WriteBaseList写入文件,复杂List<T>用函数WriteComplexList写入文件。
若为单个数据类型,则需要判断这个数据类型是自定义的复合类型(Student),还是.NET的内置简单类型,并采用不同的方式处理。
在写入功能中最重要的一个函数为:
WirteOne
private
void
WriteOne( XmlDocument document, XmlElement note,
object
item )
{
PropertyInfo[] ps
=
item.GetType().GetProperties();
//
遍历这个类的所有属性
foreach
( PropertyInfo p
in
ps )
{
object
value
=
p.GetValue( item,
null
);
//
查找这个对象下面被标志为SaveAttribute的特性
if
( IsSave( p )
&&
value
!=
null
)
{
if
( IsList( p.PropertyType ) )
{
//
List<Notify>
if
( IsHaveChild( p.PropertyType.GetGenericArguments()[
0
] ) )
//
if ( IsHaveChild( p.PropertyType ) )
{
var a
=
value
as
IList;
if
( a.Count
!=
0
)
{
XmlElement e
=
document.CreateElement( p.Name );
e.SetAttribute(
"
IsList
"
,
"
True
"
);
e.SetAttribute(
"
Type
"
, p.PropertyType.GetGenericArguments()[
0
].FullName );
//
e.SetAttribute( "Type", p.PropertyType.FullName );
WriteComplexList( document, e, value );
note.AppendChild( e );
}
}
//
List<int>
else
{
var s
=
value
as
IList;
if
( s.Count
!=
0
)
{
XmlElement list
=
document.CreateElement( p.Name );
list.SetAttribute(
"
IsList
"
,
"
True
"
);
WriteBaseList( document, list, value );
note.AppendChild( list );
}
}
}
//
单个
else
{
//
复杂类型 Notify
if
( IsHaveChild( p.PropertyType ) )
{
XmlElement e
=
document.CreateElement( p.Name );
e.SetAttribute(
"
Type
"
, p.PropertyType.FullName );
WriteOne( document, e, value );
note.AppendChild( e );
}
//
简单类型 int
else
{
XmlAttribute attr
=
document.CreateAttribute( p.Name );
attr.Value
=
p.GetValue( item,
null
).ToString();
note.SetAttributeNode( attr );
}
}
}
}
}
这个函数的document参数即为XML文档,item为一个自定义的类型对象(如s:Student),note为XML文档中一个元素结点,即item数据所在XML文档的位置。
此函数扫描类型的所有属性,先考查属性是否实现了SaveAttribute特性,再分别讨论属性的类型是上面所列四种类型中的哪一种,分别进行不同的处理。
二、读取数据
与保存数据类似,也必须分四种情况对数据进行讨论。与保存数据不同的是,在将值写入内存对象有点区别,因为写入XML文件,可直接使用.NET提供的XML处理函数来实现,而读取时就需要用.NET的反射机制来给对象赋值。
SetProperty函数实现的功能为:给对象o的name属性,赋值为value。
SetProperty
void
SetProperty(
object
o,
object
value,
string
name )
{
Type type
=
o.GetType();
PropertyInfo p
=
type.GetProperty( name );
if
( p
!=
null
)
{
p.SetValue( o, value,
null
);
}
}
下面的ReadBaseList函数实现的功能为:从XML的一系列并列节点中,创建List<T>类型的对象,其中type参数为List<T>类型的字符串表示。
ReadBaseList
object
ReadBaseList( XmlNodeList nodes,
string
type )
{
object
result
=
CreateObject( type );
MethodInfo m
=
result.GetType().GetMethod(
"
Add
"
);
//
ArrayList result = new ArrayList();
foreach
( XmlNode i
in
nodes )
{
m.Invoke( result,
new
object
[] { ConvertType( i.InnerText, GetListPropertyType( result.GetType() ) ) } );
}
return
result;
}
此函数的关键是于获得List<T>类型的Add方法,将其存储在MethInfo对象中,用此对象的Invoke方法向List<T>对象中添加元素。
现在XmlSave还不完善,比如不支持数组、不支持Stack<T>等类型的数据。并且由于使用了大量的反射,使得这个类在使用时非常耗时。现在的思路是想将反射的运行转移到设计阶段,即设计一个自动代码生成器,使之可以根据要保存的数据类型生成针对专门数据类型的XML读写代码(这个过程非常有挑战,暂时还没有去思考如何实现)。
到此简略介绍了XmlSave的实现,详细请见源代码。
如果需要在其他地方使用本程序中的代码,请保留原作者信息。