模仿.NET的序列化机制

大家都使用过.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的实现,详细请见源代码

如果需要在其他地方使用本程序中的代码,请保留原作者信息。

你可能感兴趣的:(.net)