如何有效地序列化数据?使用 sharpSerializer 对不带默认构造函数的 Bitmap、Cursor、Icon、Font 等对象进行自定义序列化






4.91/5 (6投票s)
是应该用自定义序列化器还是替代模式来序列化没有默认构造函数的类型?WP7 中将 FontFamily 序列化到 Isolated Storage 的示例。
引言
XmlSerializer、sharpSerializer 以及许多其他序列化引擎无法反序列化没有默认构造函数的类型。在本文中,我将告诉您如何有效地序列化没有默认构造函数的难处理对象。我还将解释自定义序列化器模式和替代模式之间的区别,并告诉您哪种更好以及为什么。最后,我将用 sharpSerializer(一个用于 .NET Full、.NET Compact 和 Silverlight 的开源序列化器)展示一个将不带默认构造函数的对象序列化到 WP7 的 Isolated Storage 的示例。
背景
每位母亲都说:序列化器不是内存转储器。它不应该持久化应用程序中的每一个字节。在序列化期间,应只存储重要数据。没有必要序列化对象的所有属性和字段。只需存储能够恢复对象状态所需的那些对象成员。其他成员应在序列化过程中被忽略。它们可以稍后从重要成员中恢复。这种策略有两个很大的优点:
- 序列化数据体积更小,
- 序列化速度更快。
如何序列化 Bitmap、Cursor、Icon、Font 和其他没有默认构造函数的类型
最简单的答案——不要!这些类型很复杂,具有内部句柄、流、引用,不应被完整序列化。
这些类型的自定义序列化可以通过多种方式完成。下面将介绍其中两种:
- 编写自定义序列化器(自定义序列化器模式)
- 创建具有默认构造函数的替代项(替代模式)
接下来,我将解释哪种更好以及为什么。
假设我们要序列化以下对象:
public class TypeWithoutStandardConstructor
{
// Constructor with parameters
public TypeWithoutStandardConstructor(object data){}
// Some important members
// ...
// Some unimportant members
// ...
}
模式 1 - 使用自定义序列化器进行序列化
一些第三方序列化引擎包含自定义序列化器的占位符。自定义序列化器在序列化开始之前定义并添加到此占位符(例如 Dictionary<Type, ICustomSerializer>
)中。在序列化过程中,序列化引擎会检查序列化的成员。如果一个序列化的成员是特殊类型,则会使用专用的自定义序列化器来序列化该成员。否则,默认序列化引擎会序列化该成员。
自定义序列化器可以将对象序列化到流中,并从另一个流中反序列化。它的接口可以如下所示:
public interface ICustomSerializer
{
// Stores object to the stream
void Serialize(Stream dest, object obj){}
// Creates object from the stream
object Deserialize(Stream source){}
}
下面是一个示例实现:
[SerializedType(typeof (TypeWithoutStandardConstructor)]
public class MyCustomSerializer : ICustomSerializer
{
public void Serialize(Stream dest, object data)
{
// Serialize important members and ignore unimportant
}
public object Deserialize(Stream source)
{
// Create the object instance
// deserialize important members
// and calculate unimportant ones
}
}
SerializedTypeAttribute
是序列化类型与其序列化器之间的桥梁。自定义序列化器在序列化开始之前存储在字典中。
private Dictionary<Type, ICustomSerializer> _customSerializerMappings =
new Dictionary<Type, ICustomSerializer>();
在序列化过程中,序列化引擎会检查每个序列化类型是否已定义相应的自定义序列化器。如果存在,则该类型的成员将由自定义序列化器序列化。如果找不到自定义序列化器,则该成员将由序列化引擎本身序列化。序列化引擎的工作原理超出了本文的范围。有关更多信息,请参阅 sharpSerializer 项目页面,并搜索“sharpSerializer 如何工作?”章节。
使用自定义序列化器的优点
- 无需修改要序列化的类。
缺点
- 为每个要序列化的类型搜索合适的
ICustomSerializer
会减慢序列化速度。 - 必须进行低级序列化(自定义序列化器直接序列化到流)。每种序列化格式(XML、二进制、JSON 等)都需要专用的序列化器。
- 不同的序列化引擎需要专用的自定义序列化器。没有通用的
ICustomSerializer
接口。 - 程序员必须额外编写自定义序列化器。
模式 2 - 使用替代类进行序列化
在替代模式下,序列化的不是 TypeWithoutStandardConstructor
,而是具有默认构造函数的它的替代项。
sharpSerializer 使用此模式。所有重要的属性都从原始对象复制到替代项。原始对象的字段被转换为替代项的公共属性(出于性能原因,sharpSerializer 只序列化公共属性)。
public class Substitute
{
// default constructor is required for serialization
public Substitute(){}
// creates substitute from the original
public static Substitute CreateSubstitute(
TypeWithoutStandardConstructor original)
{
// Create substitute instance,
// fill its properties from the original object,
// convert fields of the original object
// in public properties of the substitute
}
public TypeWithoutStandardConstructor CreateOriginal()
{
// create original instance, even without default constructor,
// copy all important members from the substitute,
// calculate unimportent ones
}
// Only important properties and fields of the original
public property VitalProperty1 { get; set; }
// ...
}
替代类包含对象的所有重要成员。它们在 Substitute.CreateSubstitute()
静态函数中从原始对象复制。然后序列化替代项。在反序列化过程中,会重新加载替代项。原始对象在替代项的 CreateOriginal()
函数中创建。CreateOriginal()
函数是一种对象工厂。
替代模式的优点
- 无需修改原始对象。
- 无需花费时间搜索自定义序列化器列表。
- 无需进行低级序列化。流的读取/写入由序列化引擎完成。
- 使用不同的序列化引擎时,无需调整代码。
缺点
- 程序员也必须编写替代项。
- 在序列化之前和反序列化之后,必须进行原始对象与其替代项之间的转换。
哪种更好?自定义序列化器还是替代模式?
在这两种模式下,都需要编写额外的代码。无论是自定义序列化器还是替代类。在这两种情况下,原始对象都无需修改。创建替代项会减慢序列化速度,但为每个序列化类型查询自定义序列化器字典可能是一个更大的时间开销。
替代模式的第一个确定性优势是将低级序列化留给序列化引擎。为每种序列化格式(XML、二进制、JSON)创建不同的自定义序列化器的必要性,使自定义序列化器模式的复杂性成倍增加。
第二个优势是其灵活性。我知道的每个序列化引擎都支持序列化具有默认构造函数和公共属性的类。因此,从一个序列化引擎迁移到另一个引擎没有任何问题。
在我看来,替代模式在这场竞争中明显胜出。
使用替代模式和 sharpSerializer 将 FontFamily 序列化到 WP7 的 IsolatedStorage 的示例
System.Windows.Media.FontFamily
是 Silverlight 库的一部分。它没有默认构造函数,必须以自定义方式进行序列化。
sharpSerializer 是 .NET、.NET Compact 和 Silverlight 的开源序列化器。它可以将数据序列化为 XML 和二进制格式。sharpSerializer 使用替代模式进行序列化。
下面展示了 System.Windows.Media.FontFamily
中有趣的部分:
public class FontFamily
{
public FontFamily(string familyName)
{
Source = familyName;
}
public string Source { get; set; }
}
创建了一个等效的替代类:
public class FontFamilySubstitute
{
// default constructor is required
// for the serialization
public FontFamilySubstitute()
{
}
// vital property, it will be serialized
public string FamilyName { get; set; }
// creates substitute from the FontFamily
public static FontFamilySubstitute CreateSubstitute(FontFamily fontFamily)
{
var substitute = new FontFamilySubstitute();
// Copy properties from the original object
substitute.FamilyName = fontFamily.Source;
return substitute;
}
// creates FontFamily from the substitute
public FontFamily CreateFontFamily()
{
// creating the object instance
// and initializing it
return new FontFamily(FamilyName);
}
}
辅助类简化了在 WP7 中序列化到 IsolatedStorage
的操作。
public static class IsolatedStorageSerializer
{
public static void Serialize(object data, string filename)
{
using (IsolatedStorageFile appStorage =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream file =
appStorage.OpenFile(filename, FileMode.Create))
{
// creating serializer
var sharpSerializer = new SharpSerializer();
// serialize data
sharpSerializer.Serialize(data, file);
}
}
}
public static T Deserialize<T>(string filename)
{
using (IsolatedStorageFile appStorage =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream file =
appStorage.OpenFile(filename, FileMode.Open))
{
// creating serializer
var sharpSerializer = new SharpSerializer();
// deserialize data
return (T) sharpSerializer.Deserialize(file);
}
}
}
}
最后一步是编写序列化流程。
// Creating FontFamily
var fontFamily1 = new FontFamily("Times New Roman");
// Creating Substitute
var substitute1 = FontFamilySubstitute.CreateSubstitute(fontFamily1);
// Serializing to isolated storage
IsolatedStorageSerializer.Serialize(substitute1, "font.xml");
// Deserializing from isolated storage
var substitute2 =
IsolatedStorageSerializer.Deserialize<FontFamilySubstitute>("font.xml");
// Converting to FontFamily
var fontFamily2 = substitute2.CreateFontFamily();
// Comparing
System.Diagnostics.Debug.Assert(fontFamily1==fontFamily2);
关于 sharpSerializer 的几点说明
如果您是 WP7 程序员,或者不是;-),并且需要一种简单的方法来存储应用程序设置,或者只想在 WP7 上的 Silverlight 应用程序和完整 .NET 服务之间快速序列化业务对象,那么 sharpSerializer 不会令您失望。
作为 VS 的 NuGet.org 插件的用户,在右键单击“解决方案资源管理器”中的“添加库包引用..”上下文菜单后,只需搜索“sharpserializer”即可。
CodeProject 上还有其他关于 sharpSerializer 的文章:
- 使用 sharpSerializer .NET 实现泛型字典、多维数组和继承类型的 XML 序列化
- Silverlight 中到 Isolated Storage 的二进制序列化 - BinaryFormatter vs. sharpSerializer
如果您喜欢这篇文章,请给它打 5 星。如果不喜欢,请在下方评论 ;-)
历史
- 2011 年 8 月 14 日 - 首次发布。