一个简单的序列化器/反序列化器






4.39/5 (15投票s)
2004年9月22日
3分钟阅读

108283

1027
一个简单的序列化器/反序列化器。
引言
我需要一个简单的序列化/反序列化机制来保存和加载对象的状态信息,这些对象的类型(类)是在运行时构造和编译的(请参阅我关于声明式填充 PropertyGrid的文章)。XmlSerializer
和BinaryFormatter
类都无法处理这种情况。因此,我编写了一个轻量级程序集来处理我的特定需求。
序列化
序列化器包含三个步骤
- 使用
Start()
方法初始化 - 将一个或多个对象序列化为 XML
- 使用
Finish()
方法完成该过程
初始化
这非常简单。构建一个MemoryStream
和XmlTextWriter
,并设置初始 XML 文档
public void Start()
{
ms=new MemoryStream();
xtw = new XmlTextWriter(ms, Encoding.UTF8);
xtw.Formatting=Formatting.Indented;
xtw.Namespaces=false;
xtw.WriteStartDocument();
xtw.WriteComment("Auto-Serialized");
xtw.WriteStartElement("Objects");
}
序列化器
序列化器做了一些重要的事情,并且有一些限制
- 它仅序列化可写的
public
属性和枚举。 - 它忽略数组。
- 它查找使用
[DefaultValue]
属性修饰的属性,如果该属性存在且属性值等于默认值,则不会序列化该属性。 - 它使用类型转换器将属性值转换为
string
,而不是ToString()
方法。这允许反序列化可以在Font
、Color
和其他知道如何序列化为string
的类上工作。 - 它仅序列化
property
类型中设置了IsSerializable
标志的属性。 - 它不会遍历
object
图来序列化子类。
// simple property serialization
public void Serialize(object obj)
{
Trace.Assert(xtw != null, "Must call Serializer.Start() first.");
Trace.Assert(obj != null, "Cannot serialize a null object.");
Type t=obj.GetType();
xtw.WriteStartElement(t.Name);
foreach(PropertyInfo pi in t.GetProperties())
{
Type propertyType=pi.PropertyType;
// with enum properties, IsPublic==false, even if marked public!
if ( (propertyType.IsSerializable) && (!propertyType.IsArray) &&
(pi.CanWrite) && ( (propertyType.IsPublic) || (propertyType.IsEnum) ) )
{
object val=pi.GetValue(obj, null);
if (val != null)
{
bool isDefaultValue=false;
// look for a default value attribute.
foreach(object attr in pi.GetCustomAttributes(false))
{
if (attr is DefaultValueAttribute)
{
// it exists--compare current value to default value
DefaultValueAttribute dva=(DefaultValueAttribute)attr;
isDefaultValue=val.Equals(dva.Value);
}
}
// only non-default values or properties without a default value are
// serialized.
if (!isDefaultValue)
{
// do a type conversion to a string, as this yields a
// deserializable value, rather than what ToString returns.
TypeConverter tc=TypeDescriptor.GetConverter(propertyType);
if (tc.CanConvertTo(typeof(string)))
{
val=tc.ConvertTo(val, typeof(string));
xtw.WriteAttributeString(pi.Name, val.ToString());
}
else
{
Trace.WriteLine("Cannot convert "+pi.Name+" to a string value.");
}
}
}
else
{
// null values not supported!
}
}
}
xtw.WriteEndElement();
}
完成器
Finish()
方法清理文本编写器并返回 XML
public string Finish()
{
Trace.Assert(xtw != null, "Must call Serializer.Start() first.");
xtw.WriteEndElement();
xtw.Flush();
xtw.Close();
Encoding e8=new UTF8Encoding();
xml=e8.GetString(ms.ToArray(), 1, ms.ToArray().Length-1);
return xml;
}
反序列化
反序列化器期望实例已经构建。这是一个非常有用的快捷方式,因为在运行时构造对象通常需要完全限定的程序集名称、命名空间和其他信息。此外,我的运行时构造类的类型信息实际上不可用 - 只有实例可用。对于我的特定要求,这不是问题。
此外,反序列化器将检查每个属性的默认值,并将该值恢复到指定的对象,除非它在 XML 中被覆盖。
最后,当反序列化多个对象时,您必须知道用于序列化对象的确切顺序,因为指向序列化对象元素的索引会传递给反序列化器。同样,对于我的目的而言,此限制不是问题。
// simple property deserialization
public void Deserialize(object obj, int idx)
{
Trace.Assert(doc != null, "Must call Deserializer.Start() first.");
Trace.Assert(doc.ChildNodes.Count==3, "Incorrect xml format.");
Trace.Assert(idx < doc.ChildNodes[2].ChildNodes.Count,
"No element for the specified index.");
Trace.Assert(obj != null, "Cannot deserialize to a null object");
// skip the encoding and comment, and get the indicated
// child in the Objects tag
XmlNode node=doc.ChildNodes[2].ChildNodes[idx];
Type t=obj.GetType();
Trace.Assert(t.Name==node.Name, "Object name does not match element tag.");
// set all properties that have a default value and not overridden.
foreach(PropertyInfo pi in t.GetProperties())
{
Type propertyType=pi.PropertyType;
// look for a default value attribute.
foreach(object attr in pi.GetCustomAttributes(false))
{
if (attr is DefaultValueAttribute)
{
// it has a default value
DefaultValueAttribute dva=(DefaultValueAttribute)attr;
if (node.Attributes[pi.Name] == null)
{
// assign the default value, as it's not being overridden.
// this reverts the object's property back to the default
pi.SetValue(obj, dva.Value, null);
}
}
}
}
// now parse the xml attributes that are going to change property values
foreach(XmlAttribute attr in node.Attributes)
{
string pname=attr.Name;
string pvalue=attr.Value;
PropertyInfo pi=t.GetProperty(pname);
if (pi != null)
{
TypeConverter tc=TypeDescriptor.GetConverter(pi.PropertyType);
if (tc.CanConvertFrom(typeof(string)))
{
try
{
object val=tc.ConvertFrom(pvalue);
pi.SetValue(obj, val, null);
}
catch(Exception e)
{
Trace.WriteLine("Setting "+pname+" failed:\r\n"+e.Message);
}
}
}
}
}
用法
用法非常简单。假设我们要序列化一个简单的类(在本例中,一个在编译时构造的类)
public class TestClass
{
protected string firstName;
protected string lastName;
[DefaultValue("Marc")]
public string FirstName
{
get {return firstName;}
set {firstName=value;}
}
[DefaultValue("Clifton")]
public string LastName
{
get {return lastName;}
set {lastName=value;}
}
public TestClass()
{
firstName="Marc";
lastName="Clifton";
}
}
序列化此类的实例将如下所示
Serializer s=new Serializer();
s.Start();
TestClass tc=new TestClass();
tc.FirstName="Joe";
tc.LastName="Smith";
s.Serialize(tc);
string text=s.Finish();
生成的 XML 如下所示
<?xml version="1.0" encoding="utf-8"?>
<!--Auto-Serialized-->
<Objects>
<TestClass FirstName="Joe" LastName="Smith" />
</Objects>
此对象的反序列化如下所示
Deserializer d=new Deserializer();
d.Start(text);
TestClass tc=new TestClass();
d.Deserialize(tc, 0);
之后,该类的属性设置为“Joe
”和“Smith
”。
修订
- 2004 年 11 月 30 日 - 增加了对实现
IList
的属性类型的支持
结论
上面提供的代码满足了我非常具体的要求。即使您没有此要求,也希望您能从所演示的技术中获得一些收获,尤其是使用类型转换器来转换为和从string
转换。这是一个重要的“技巧”,可确保序列化的string
采用反序列化器可以处理的格式,并避免为Font
、Color
和其他对象编写特殊情况代码。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。