关于程序员:序列化和反序列化

5次阅读

共计 12227 个字符,预计需要花费 31 分钟才能阅读完成。

什么是序列化和反序列化?

序列化和反序列化是对象和字节流之间的互相转换,将一个对象或者对象图转换成一个字节流的过程就是序列化;反之将一个字节流转换回对象图的过程便是反序列化。

序列化和反序列化的作用(包含但不限于)
  • 将对象克隆并作为备份
  • 将对象通过网络传输给另一台机器
  • 加密和压缩数据
简略的序列化与反序列化示例
private static MemoryStream SerializeToMemory(Object objectGraph)
{
    // 结构一个流包容序列化对象
    var stream = new MemoryStream();

    // 结构序列化格式化器
    var formatter = new BinaryFormatter();

    // 通知格式化器将对象序列化到一个流中
    formatter.Serialize(stream, objectGraph);

    // 返回序列化好的对象流
    return stream;
}

private static Object DeserializeFromMemory(Stream stream)
{
    // 结构序列化格式化器
    var formatter = new BinaryFormatter();

    // 通知格式化器从流中反序列化对象
    return formatter.Deserialize(stream);
}

static void Main()
{var objectGraph = new List<string> { "Cat", "Dog", "Fish"};
    var stream = SerializeToMemory(objectGraph);

    // 重置
    stream.Position = 0;
    objectGraph = null;

    // 反序列化
    objectGraph = (List<string>)DeserializeFromMemory(stream);
    foreach (var item in objectGraph)
    {Console.WriteLine("item:{0}", item);
    }

    Console.ReadKey();}

运行后果

格式化器晓得如何序列化和反序列化一个对象,想要序列化、反序列化一个对象,只须要调用格式化器的 Serialize 或 Deserialize 办法。

**Serialize:** 格式化器参考对每个对象的类型进行形容的元数据,从而理解如何序列化实现的对象。序列化时 Serialize 办法利用反射来查看每个对象的类型中都有哪些实例字段,在这些字段中任何一个援用了其它类型的字段,格式化器就晓得也要对这些被援用了的字段进行序列化。

同时格式化器的算法也十分智能,能确保对象图中的每一个对象只序列化一次,如果对象图中的两个对象互相援用,在每个对象之序列化一次的前提下,便可能防止进入有限循环。

**Deserialize:** 该办法会查看流的内容,结构流中的所有对象的实例,并初始化所有这些对象中的字段,使它们具备与当初序列化时雷同的值。

序列化一个对象时,类型的全名和类型的定义程序集的名称也会被写入流。默认状况下 BinaryFormatter 会输入程序集的残缺标识(无扩展名的文件名、版本号、语言文化和公钥信息)。反序列化一个对象时,格式化器首先获取程序集标识信息,并通过 Assembly 的 Load 办法,确保程序集加载到 AppDomain 中。程序集加载好之后,格式化器在程序集中查找与要反序列化的对象匹配的一个类型。如果程序集不蕴含要查找的类型,则抛出异样,并终止序列化。反之就创立类型的一个实例,并用流中蕴含的值对其字段进行初始化。如果类型中的字段与流中读取的字段名不齐全匹配,抛出 SerializationException 异样,并终止序列化。

序列化类型

类型默认是无奈序列化的

class Poiont
{
    public int x;
    public int y;
}

static void Main()
{var poiont = new Poiont() {x = 100, y = 100};
    using(var stream = new MemoryStream())
    {new BinaryFormatter().Serialize(stream, poiont); // 异样
    }
}


序列化一个对象图时,格式化器会查看每个对象的类型都是可序列化的。如果对象图中的任意一个对象不是可序列化的,格式化器的 Serialize 办法都会抛出 SerializationException 异样

使类型可序列化,让上述代码不再抛出异样

[Serializable]
class Poiont
{
    public int x;
    public int y;
}

序列化一个对象图时,可能存在有些对象能够被序列化,有些对象不能序列化的状况。出于性能格式化器不会在序列化前查看对象图中的所有对象都可序列化。所以在序列化对象图过程中抛出 SerializationException 异样,齐全可能曾经有有一部分对象曾经序列化到了流中。

SerializableAttribute 这个定制 attribute 的两个特色:
  • 只能利用于援用类型(class)、值类型(struct)、枚举类型和委托类型,其中枚举类型和委托类型总是可序列化的,不用显示利用 SerializableAttribute
  • 不会被派生类继承
[Serializable]
class super { } // 可序列化
class child : super {} // 不可序列化
管制序列化和反序列化

SerializableAttribute 利用于类型时,所有实例字段 (private,protected,public 等) 都会被序列化。然而类型可能定义了一些不用序列化的实例字段(如序列化后信息有效或者通过简略的计算便能取得),如下代码展现了如何管制序列化和反序列化

[Serializable]
class Rectangle
{
    private float m_Width;
    public float Width
    {get { return this.m_Width;}
    }

    private float m_Height;
    public float Height
    {get { return this.m_Height;}
    }

    [NonSerialized]
    private float m_Area;
    public float Area
    {get { return this.m_Area;}
    }

    public Rectangle(float w, float h)
    {
        this.m_Width = w;
        this.m_Height = h;
        this.m_Area = w * h;
    }
}

上述代码中 Rectangle 的对象能够序列化,且格式化器只会序列化对象的 m_Width 和 m_Height 字段的值,因为 m_Area 字段应用了 NonSerializedAttribute,所以 m_Area 字段的值不会被序列化。

static void Main()
{using(var stream = new MemoryStream())
    {var formatter = new BinaryFormatter();
        formatter.Serialize(stream, new Rectangle(10, 5));

        stream.Position = 0;
        var rectangle = (Rectangle)formatter.Deserialize(stream);
        Console.WriteLine("w = {0}, h = {1}, area = {2}", rectangle.Width, rectangle.Height, rectangle.Area);
    }
}

后果

运行后果证实 m_Area 字段的确不会被序列化,因为反序列化的时候 Area 的值被初始化化为 0 而非 50,但这有时候并不是所冀望的,以下代码展现了如何修改该问题:

[Serializable]
class Rectangle
{
    //...
    // 在原 Rectangle 类上扩大 MyOnDeserialized 办法
    [OnDeserialized]
    private void MyOnDeserialized(StreamingContext context)
    {this.m_Area = this.m_Width * this.m_Height;}
}

再次执行以下代码段:

static void Main()
{using(var stream = new MemoryStream())
    {var formatter = new BinaryFormatter();
        formatter.Serialize(stream, new Rectangle(10, 5));

        stream.Position = 0;
        var rectangle = (Rectangle)formatter.Deserialize(stream);
        Console.WriteLine("w = {0}, h = {1}, area = {2}", rectangle.Width, rectangle.Height, rectangle.Area);
    }
}

后果

批改后的代码仅仅只是增加了一个应用 OnDeserializedAttribute 标记的 MyOnDeserialized 办法,每次反序列化类型的一个实例时,格式化器都会查看类型中是否定义了一个利用了 OnDeserializedAttribute 的办法。如果是就会调用这个办法,调用这个办法的时候所有可序列化的字段都会被正确的赋值。在该办法中,能够拜访这些字段来进行一些额定的工作,从而保障对象的齐全反序列化。

除了 OnDeserializedAttribute,命名空间 System.Runtime.Serialization 中还定义了 OnSerializingAttribute、OnSerializedAttribute、OnDeserializingAttribute。序列化一组对象时,格式化器首先调用对象的标记了 OnSerializingAttribute 的所有办法。接着序列化对象的所有字段。最初调用对象的标记了 OnSerializedAttribute 的所有办法。同理在反序列化一组对象时,格式化器先调用对象的标记了 OnDeserializingAttribute 的所有办法,而后反序列化对象的所有字段,最初调用对象的标记了 OnDeserializedAttribute 的所有办法。

*** 注:** 在反序列化期间,格式化器发现一个类型中蕴含利用了 OnDeserializedAttribute 的办法时,格式化器会将这个对象的援用增加到一个外部列表中。等所有对象反序列化之后,格式化器以相同的方向遍历这个列表(相同顺序调用的起因是内层对象先于外层对象完结反序列化),调用每个对象的 OnDeserializedAttribute 标记办法,调用这个办法后,所有可序列化的字段都会被正确设置。

FormatterServices 如何序列化和反序列化类型实例?

序列化

  • 调用 FormatterServices 的 GetSerializableMembers 办法,该办法利用反射获取类型的 public 和 private 实例字段,办法返回 MemberInfo 数组,每个元素对应一个可序列化的实例字段
  • MemberInfo 数组传给 FormatterServices 的 GetObjectData 办法,返回 Object 数组,每个元素都标识了被序列化的那个对象中的一个字段的值。Object 数组和 MemberInfo 数组是并行的(Object 数组的 0 元素便是 MemberInfo 数组 0 元素的值)
  • 格式化器将程序集标识和类型的残缺名称写入流
  • 格式化器遍历两个数组中的元素,将每个成员的名称和值写入流

反序列化

  • 格式化器从流中读取程序集标识和残缺类型名称,如果程序集以后没有加载到 AppDomain 中,就加载它。如果程序集不能被加载就抛出异样,程序集已加载格式化器将程序集标识信息和类型全名传给 FormatterServices 的 GetTypeFromAssembly 办法,办法返回一个 Type 对象,代表要反序列化的对象类型
  • 调用 FormatterServices 的 GetUninitializedObject 办法,该办法为一个新对象分配内存,不会调用对象的结构器,对象的所有字段都会被初始化为 0 或 null
  • 调用 FormatterServices 的 GetSerializableMembers 办法结构并初始化一个 MemberInfo 数组,办法返回序列化好的当初须要反序列化的一组字段
  • 格式化器依据流中蕴含的数据创立并初始化一个 Object 数组
  • 对新调配的对象、MemberInfo 数组以及 Object 数组 (蕴含字段的值) 的援用传给 FormatterServices 的 PopulateObjectMembers 办法,这个办法会遍历数组,将每个字段初始化成对应的值,到此反序列化完结

示例代码

static void Main()
{var assembly = Assembly.Load("MySerialize");
    var type = FormatterServices.GetTypeFromAssembly(assembly, "MySerialize.Rectangle");
    var obj = FormatterServices.GetUninitializedObject(type);
    var mf = FormatterServices.GetSerializableMembers(type);
    var data = FormatterServices.GetObjectData(new Rectangle(10, 20), mf);
    var rect = (Rectangle)FormatterServices.PopulateObjectMembers(obj, mf, data);
    Console.WriteLine("w = {0}, h = {1}", rect.Width, rect.Height);
    Console.ReadKey();}

运行后果

ISerializable 管制序列化和反序列化

因为格式化器外部会应用反射,而反射的速度是比较慢的,所以会减少序列化和反序列化所花的工夫。能够通过让类型实现 ISerializable 接口防止应用反射也能够序列化和反序列化对象。值得注意的是类型一旦实现了 ISerializable 接口,它的派生类也必须实现它,并且保障派生类调用了基类的 GetObjectData 办法。

格式化器在序列化对象图查看每个对象时,如果发现类型实现了 ISerializable 接口,便会疏忽所有定制 attribute,改为结构一个新的 SerializationInfo 对象,这个对象蕴含了理论要为对象序列化的值的汇合。

结构并初始化好 SerializationInfo 对象后,格式化器调用对象的 GetObjectData 办法,并传递给办法 SerializationInfo 对象的援用。GetObjectData 办法负责决定须要哪些信息来序列化对象,并将这些信息增加到 SerializationInfo 对象中,GetObjectData 调用 SerializationInfo 类型提供的 AddValue 办法指定须要序列化的信息,对每个想要序列化的数据都要调用一次 AddValue。

示例代码

[Serializable]
class Square : ISerializable, IDeserializationCallback
{
    private int m_Area;
    public int Area
    {get { return this.m_Area;}
    }

    private int m_Length;
    public int Length
    {get { return this.m_Length;}
    }

    private string m_Name;
    public string Name
    {get { return this.m_Name;}
    }

    // 只用于反序列化
    private SerializationInfo m_SiInfo;

    public Square(string name, int len)
    {
        this.m_Length = len;
        this.m_Name = name;
    }

    // 管制反序列化的非凡结构器
    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    protected Square(SerializationInfo info, StreamingContext context)
    {
        // 反序列化期间为 OnDeserialization 保留 SerializationInfo
        // 在这里不能保障对象曾经齐全被序列化
        this.m_SiInfo = info;
    }

    // 管制序列化的办法
    [SecurityCritical]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {info.AddValue("Length", this.m_Length);
        info.AddValue("Name", this.m_Name);
    }

    // 所有字段都被反序列化好之后调用该办法
    public virtual void OnDeserialization(object sender)
    {if (this.m_SiInfo == null)
            return;

        this.m_Length = this.m_SiInfo.GetInt32("Length");
        this.m_Name = this.m_SiInfo.GetString("Name");
        this.m_Area = (int)Math.Pow(this.m_Length, 2);
    }
}

static void Main()
{using (var stream = new MemoryStream())
    {var formatter = new BinaryFormatter();
        formatter.Serialize(stream, new Square("正方形", 10));

        stream.Position = 0;
        var square = (Square)formatter.Deserialize(stream);
        Console.WriteLine("边长:{0}的 {1} 的面积是:{2}", square.Length, square.Name, square.Area);
    }
    Console.ReadKey();}

运行后果

在反序列化的时候,格式化器从流中提取对象时,会为新对象调配新的内存(通过 GetUninitializedObject 办法),新对象的所有字段都设置为 0 或 null。而后格式化器查看类型是否实现了 ISerializable 接口,如果是格式化器就尝试调用一个参数和 GetObjectData 办法统一的非凡结构器。

结构器获取一个 SerializationInfo 对象的援用,这个对象蕴含了对象序列化时增加的所有值,反序列化对象的字段时,能够调用和对象序列化时传给 AddValue 办法的值的类型匹配的一个 Get 办法,办法的返回值可用于初始化新对象的各个字段。

基类没有实现 ISerializable 接口,如何定义一个实现它的类型?

如果基类同样实现了 ISerializable 接口,那么只有调用基类的 GetObjectData 办法便能实现序列化。一旦基类没有实现 GetObjectData 接口,这种状况下派生类必须手动序列化基类的值。弊病:如果基类的值是 private 字段,那么基本就无奈实现。

示例代码

[Serializable]
class Base
{
    protected string m_Name = "DoubleJ";

    public Base() {}
}

[Serializable]
class Child : Base, ISerializable
{
    private DateTime m_DateTime = DateTime.Now;

    public Child() {}

    protected Child(SerializationInfo info, StreamingContext context)
    {
        // 获取类和基类可序列化成员汇合
        var baseType = this.GetType().BaseType;
        var mi = FormatterServices.GetSerializableMembers(baseType, context);

        // 反序列化基类字段
        for (int i = 0; i < mi.Length; i++)
        {var fi = (FieldInfo)mi[i];
            fi.SetValue(this, info.GetValue(baseType.FullName + "." + fi.Name, fi.FieldType));
        }

        this.m_DateTime = info.GetDateTime("Date");
    }

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {info.AddValue("Date", this.m_DateTime);

        // 获取类和基类可序列化成员汇合
        var baseType = this.GetType().BaseType;
        var mi = FormatterServices.GetSerializableMembers(baseType, context);

        // 序列化基类字段到 info 对象
        for (int i = 0; i < mi.Length; i++)
            info.AddValue(baseType.FullName + "." + mi[i].Name, ((FieldInfo)mi[i]).GetValue(this));
    }

    public override string ToString()
    {return string.Format("Name = {0}, Date = {1}", this.m_Name, this.m_DateTime);
    }
}

static void Main()
{using (var stream = new MemoryStream())
    {var formatter = new BinaryFormatter();
        formatter.Serialize(stream, new Child());

        stream.Position = 0;
        var o = (Child)formatter.Deserialize(stream);
        Console.WriteLine(o.ToString());
    }
    Console.ReadKey();}

运行后果

序列化代理
[Serializable]
class Version1Type
{public Int32 x;}

// 定义代理类
class MySerializationSurrogate : ISerializationSurrogate
{public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {info.AddValue("x", ((Version1Type)obj).x);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {((Version1Type)obj).x = info.GetInt32("x");
        return obj;
    }
}

static void Main()
{using (var stream = new MemoryStream())
    {var formatter = new BinaryFormatter();

        // 结构代理选择器对象
        var ss = new SurrogateSelector();

        // 通知代理选择器为 Version1Type 对象应用咱们的代理
        ss.AddSurrogate(typeof(Version1Type), formatter.Context, new MySerializationSurrogate());

        // 通知格式化器应用代理选择器
        formatter.SurrogateSelector = ss;

        // 序列化
        var beforeSerializeData = new Version1Type {x = 6};
        formatter.Serialize(stream, beforeSerializeData);

        // 反序列化
        stream.Position = 0;
        var afterSerializeData = (Version1Type)formatter.Deserialize(stream);

        Console.WriteLine("beforeSerializeX = {0}", beforeSerializeData.x);
        Console.WriteLine("afterSerializeX = {0}", afterSerializeData.x);
    }
}

运行后果

应用代理类型后,调用格式化器的 Serialize 办法时,会在 SurrogateSelector 保护的哈希表中查找要序列化的每个对象的类型,如果发现匹配类型就调用 ISerializationSurrogate 对象的 GetObjectData 办法获取应该写入流的信息。

调用格式化器的 Deserialize 办法时,会在 SurrogateSelector 中查找要反序列化的对象类型,如果发现匹配类型就调用 ISerializationSurrogate 对象的 SetObjectData 办法来设置反序列化的对象中的字段。

SurrogateSelector 对象在外部保护了一个公有哈希表,调用 AddSurrogate 办法时,Type 和 StreamingContext 形成哈希表的 Key,ISerializationSurrogate 对象便是哈希表的 Value。如果要增加的 Key 曾经存在,则会抛出 ArgumentException。

如何将对象反序列化成另一个类型?

利用 SerializationBinder 类能够很不便地将一个对象反序列化成一个不同的类型,首先要定义好本人的类型如:MySerializationBinder 类(名字任意),接着在结构好格式化器后,设置格式化器的 Binder 属性让它的值等于 MySerializationBinder 类型实例。在反序列化期间,格式化器发现设置了绑定器,对象在反序列化时格式化器都会调用绑定器的 BindToType 办法,并向办法传递程序集名称以及格式化器想要反序列化的类型。在外部可实现本人的逻辑返回理论想要反序列化的类型。

示例代码

[Serializable]
class Version1Type
{public int x;}

[Serializable]
class Version2Type : ISerializable
{
    public int x;
    public string name;

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {info.AddValue("x", x);
        info.AddValue("name", name);
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    private Version2Type(SerializationInfo info, StreamingContext context)
    {x = info.GetInt32("x");
        try
        {name = info.GetString("name");
        }
        catch (SerializationException)
        {name = "default value";}
    }
}

class MySerializationBinder : SerializationBinder
{public override Type BindToType(string assemblyName, string typeName)
    {var assemVer1 = Assembly.GetExecutingAssembly().FullName;
        var typeVer1 = "MySerialize.Version1Type";
        if (assemblyName == assemVer1 && typeName == typeVer1)
            typeName = "MySerialize.Version2Type";

        //typeToDeserialize
        return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
    }
}

static void Main()
{using (var stream = new MemoryStream())
    {var formatter = new BinaryFormatter();

        // 序列化
        Console.WriteLine("序列化的对象类型:" + typeof(Version1Type));
        formatter.Serialize(stream, new Version1Type() {x = 10});
        formatter.Binder = new MySerializationBinder();

        stream.Position = 0;
        var type2 = (Version2Type)formatter.Deserialize(stream);

        Console.WriteLine("反序列化的对象类型:" + type2.GetType());
        Console.WriteLine("x = {0}, name = {1}", type2.x, type2.name);
    }
    Console.ReadKey();}

运行后果

正文完
 0