第十章 自定义序列化类
按理说,这个内容应该是不值得来说的,因为已经存在很多的方法来对类进行序列化。即使是自定义的序列化,也存在很多的方法。我们最常见的就是通过xml的xmlserializer方式来序列化。也可以使用iSerializable这个接口来实现自动化的序列化。
即使是手工来写序列化,每个人也会采用不同的方式。比如类xml的格式,或者json格式,还有其他的类似CSV键值对等。所谓CSV
是Comma Separated Value(逗号分隔值)的英文缩写,即记录通过逗号来把所有的属性值分割,解析的时候利用String.Split方法快速建立属性和值的关系。
现在来简单的说明几种序列化可能存在的问题。
- 使用xmlSerialize。优点,自动化实现。缺点,写一些xmlIngore,还要注意某些属性不能被序列化情况,还要类必须存在无参的构造函数。
- 使用ISerialiable。优点,自动实现。缺点,某些字段要写一堆的Attribute。
- 使用类似CSV的键值方式。优点,自己控制每一个字段的存储形式,比较灵活。缺点,需要对每一个属性进行编写代码。还要注意到分隔符的问题。
现在我们要下面这个类,来进行字符方式的自定义序列化的演示。
class HomeInfo
{
#region 属性
public int StreetCode { get; set; }
public string HomeName { get; set; }
public PersonInfo Person { get; set; }
#endregion
#region 保存
/// <summary>
/// HomeInfo进行自定义序列化值
/// </summary>
/// <param name="splitChar"></param>
/// <returns></returns>
public string SerializeValue(char splitChar)
{
StringBuilder sb = new StringBuilder();
sb.Append("StreetCode=" + StreetCode);
sb.Append(splitChar);
sb.Append("HomeName=" + HomeName);
sb.Append(splitChar);
sb.Append("Person=" + Person.SerializeValue(splitChar));
return sb.ToString();
}
#endregion
}
class PersonInfo
{
#region 属性
public string Name { get; set; }
public int Age { get; set; }
public string PersonProfile { get; set; }
#endregion
public string SerializeValue(char splitChar)
{
StringBuilder sb = new StringBuilder();
sb.Append("Name=" + Name); sb.Append(splitChar);
sb.Append("Age=" + Age); sb.Append(splitChar);
sb.Append("PersonProfile=" + PersonProfile);
return sb.ToString();
}
}
由于类的属性的值千差万别,不能保证是否值中存在一些特殊字符串或者字符。所以在存储的时候我们对要存储的数据进行转义。比如我们打算用","来表示分割符,那么上面的程序得到的结果就会存在问题。因为无法区分HomeInfo和PersonInfo各自的逗号的位置。
比如测试代码如下:
<pre name="code" class="csharp">public static void Test()
{
HomeInfo home = new HomeInfo();
home.StreetCode = 10992;
home.HomeName = "Tom's House";
home.Person = new PersonInfo();
home.Person.Age = 42;
home.Person.Name = "Tomas jim";
home.Person.PersonProfile = "出生在美国的德州,从小喜欢养鱼..爱好嘛..";
string s = home.SerializeValue(',');
//s="StreetCode=10992,HomeName=Tom's House,Person=Name=Tomas jim,Age=42,PersonProfile=出生在美国的德州,从小喜欢养鱼..爱好嘛..
string[] strSplit = s.Split(',');
//StreetCode=10992
//HomeName=Tom's House
//Person=Name=Tomas jim -----从这开始解析就错误了
//Age=42
//PersonProfile=出生在美国的德州,从小喜欢养鱼..爱好嘛..
}
上面的问题出在属性中包含了类要使用的分隔符,在这里Person属性包含了HomeInfo类使用的分隔符“,”,从而导致属性值和本类的值无法区分。所以我们要做的是,如果属性中已经存在一个字符,那么这个字符不能在被上层类使用了。
比如Person属性已经使用了",",那么HomeInfo就应该换一种风格符。要寻找可用的分隔符,可用先从特殊的可打印的字符开始。
char[] nSplitChars = new char[] { ';', ',', ':', '=', '&', '.', '|', '@', '#', '$', '%', '^', '*', '[', ']' };如果这些特殊的字符在属性中用到了,那么就开始使用通用字符'a~z‘,’A~Z','0~9';一般来说不可能把所有的字符都用到了,总会有一个合适的字符可用用来作为分隔符的。
比如Person属性中使用,作为分隔符,HomeInfo使用;作为分隔符。
由于定义的分隔符是查找出来的,所以必须在头部添加一个标签标明分隔符是什么。我会在最前面添加split:[具体的分隔符]来记录。
我们重写下HomeInfo和PersonInfo的SerializeValue函数
/// <summary>
/// HomeInfo进行自定义序列化值
/// </summary>
/// <returns></returns>
public string SerializeValue()
{
List<string> s = new List<string>();
s.Add("StreetCode=" + StreetCode);
s.Add("HomeName=" + HomeName);
s.Add("Person=" + Person.SerializeValue());
return StringSplitHelper.FindSplitAndAppend(s);
}
/// <summary>
/// PersonInfo进行自定义序列化值
/// </summary>
/// <returns></returns>
public string SerializeValue()
{
List<string> s = new List<string>();
s.Add("Name=" + Name);
s.Add("Age=" + Age);
s.Add("PersonProfile=" + PersonProfile);
return StringSplitHelper.FindSplitAndAppend(s);
}
其中StringSplitHelper类能够找到一个为使用的字符作为分隔符,并组合成一个字符串。得到的最后结果是
Split:&StreetCode=10992&HomeName=Tom's House&Person=Split:;Name=Tomas jim;Age=42;PersonProfile=出生在美国的德州,从小喜欢养鱼..爱好嘛..这个需要手工编辑下就可读了。
//Split:&
//StreetCode=10992&
//HomeName=Tom's House&
//Person=
// Split:;
// Name=Tomas jim;
// Age=42;
// PersonProfile=出生在美国的德州,从小喜欢养鱼..爱好嘛..
上面的Person寻找到使用;使用分隔符,而HomeInfo使用&使用分隔符。由此,自定义方式的序列化类方式的问题解决。至于读,首先读头部的信息,获得分隔符,然后利用String.Split的方法获得属性和值的对。依次到属性,整个类的反序列化也就解决了。