65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.39/5 (15投票s)

2004年9月22日

3分钟阅读

viewsIcon

108283

downloadIcon

1027

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

引言

我需要一个简单的序列化/反序列化机制来保存和加载对象的状态信息,这些对象的类型(类)是在运行时构造和编译的(请参阅我关于声明式填充 PropertyGrid的文章)。XmlSerializerBinaryFormatter类都无法处理这种情况。因此,我编写了一个轻量级程序集来处理我的特定需求。

序列化

序列化器包含三个步骤

  1. 使用Start()方法初始化
  2. 将一个或多个对象序列化为 XML
  3. 使用Finish()方法完成该过程

初始化

这非常简单。构建一个MemoryStreamXmlTextWriter,并设置初始 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()方法。这允许反序列化可以在FontColor和其他知道如何序列化为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采用反序列化器可以处理的格式,并避免为FontColor和其他对象编写特殊情况代码。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.