大家都使用过.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类型是自定义类型的代表,其属性不具有实际意义):
{
[Save( "" )]
publicstring 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()特性,这是个自定义的特性,只所以需要这个类型是因为可以让用户决定该保存类型的哪些属性,只有加上这个标志的属性才会被保存到文件中。
<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……
二、"List<int>"简单类型的List<T>泛型集合
<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"自定义类型
<root Type="XBC.Student" Name="xbc" Age="23"/>
四、"List<Student>"自定义类型的泛型List<T>类型
<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类型:
{
[Save( "" )]
publicstring Name { get; set; }
List<StudentTime> _times =new List<StudentTime>();
[Save( "" )]
{
get { return _times; }
set { _times = value; }
}
}
class StudentTime
{
[Save( "" )]
publicint Year { get; set; }
[Save( "" )]
publicint Month { get; set; }
}
然后再定义List<Student> ss对象,生成的XML文件格式为:
<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的内置简单类型,并采用不同的方式处理。
在写入功能中最重要的一个函数为:
{
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。
{
Type type = o.GetType();
PropertyInfo p = type.GetProperty( name );
if ( p !=null )
{
p.SetValue( o, value, null );
}
}
下面的ReadBaseList函数实现的功能为:从XML的一系列并列节点中,创建List<T>类型的对象,其中type参数为List<T>类型的字符串表示。
{
object result = CreateObject( type );
MethodInfo m = result.GetType().GetMethod( "Add" );
//ArrayList result = new ArrayList();
foreach ( XmlNode i in nodes )
{
m.Invoke( result, newobject[] { ConvertType( i.InnerText, GetListPropertyType( result.GetType() ) ) } );
}
return result;
}
此函数的关键是于获得List<T>类型的Add方法,将其存储在MethInfo对象中,用此对象的Invoke方法向List<T>对象中添加元素。
现在XmlSave还不完善,比如不支持数组、不支持Stack<T>等类型的数据。并且由于使用了大量的反射,使得这个类在使用时非常耗时。现在的思路是想将反射的运行转移到设计阶段,即设计一个自动代码生成器,使之可以根据要保存的数据类型生成针对专门数据类型的XML读写代码(这个过程非常有挑战,暂时还没有去思考如何实现)。
到此简略介绍了XmlSave的实现,详细请见源代码。
如果需要在其他地方使用本程序中的代码,请保留原作者信息。