Dotnet反序列化入门各概念

目录

Dotnet在国内不算多,国内java安全偏多一点。 但是国外Dotnet很火,很多公司都是纯微软技术栈的,所以研究Dotnet还是有必要的。

本文默认读者有c#语法基础

Dotnet反序列化

本文很大程度参考了Y4er师傅的文章:https://github.com/Y4er/dotnet-deserialization/blob/main/dotnet-serialize-101.md

本小节很大程度参考了Y4er师傅的

先来一个最基本的Dotnet反序列化demo,了解一下Serializable、NonSerialized特性以及序列化、反序列化会用到的函数等。

using System;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleAppi1;

[Serializable]   //一个需要被序列化的类需要加上Serializable特性
public class Person
{
    public int age=10;
    [NonSerialized]public String name="tom";  //NonSerialized特性用于标识不需要被序列化的属性
    public String sex = "Male";
}


public class MyObject
{
    static void Main(string[] args)
    {
        Person p1 = new Person();
        FileStream fstream = new FileStream("E:/tools/dotnet/hellowold/Solution1/ser.bin", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);

        BinaryFormatter binFormatterS = new BinaryFormatter();
        binFormatterS.Serialize(fstream,p1);

        BinaryFormatter binFormatterD = new BinaryFormatter();
        fstream.Position = 0; //不加这个可能会报错
        Person p2 = (Person)binFormatterD.Deserialize(fstream);

        Console.WriteLine("age:"+p2.age);
        Console.WriteLine("name:"+p2.name);
        Console.WriteLine("sex:"+p2.sex);
    }
}

查看序列化的数据,发现采用0001 0000开头

Formatter

Formatter用于设置序列化的格式,如我们上面用到了BinaryFormatter 进行二进制序列化,除此之外还有很多Formatter

如: SoapFormatter 用于序列化soap格式 LosFormatter 用于序列化 Web 窗体页的视图状态 XmlSerializer 用于生成XML 等

所有Formatter都最终继承自IFormatter接口,我们看看这个接口

可以发现定义了序列化、反序列化方法以及三个属性,通过这三个属性可以控制序列化、反序列化的过程。我们重点关注SurrogateSelector属性。

类 字段名 含义用途
ISurrogateSelector SurrogateSelector 序列化代理选择器 接管formatter的序列化或反序列化处理
SerializationBinder Binder 用于控制在序列化和反序列化期间使用的实际类型
StreamingContext Context 序列化流上下文 其中states字段包含了序列化的来源和目的地

BinaryFormatter序列化的生命周期和事件

ISerializable

我们先来看看实现ISerializable 接口的类 序列化、反序列化调用流程

using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;

namespace ConsoleAppi1;
[Serializable]
public class Person:ISerializable
{

    public Person()
    {

    }
    //必须重载一个如下参数的构造方法,不重载的话会在反序列化时报错
    protected Person(SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("Constructor Begin!");
    }


    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("GetObjectData");
    }

    [OnDeserializing]
    private void TestOnDeserializing(StreamingContext sc)
    {
        Console.WriteLine("TestOnDeserializing");

    }
    [OnDeserialized]
    private void TestOnDeserialized(StreamingContext sc)
    {
        Console.WriteLine("TestOnDeserialized");
    }
    [OnSerializing]
    private void TestOnSerializing(StreamingContext sc)
    {
        Console.WriteLine("TestOnSerializing");
    }
    [OnSerialized]
    private void TestOnSerialized(StreamingContext sc)
    {
        Console.WriteLine("TestOnSerialized");
    }
}


public class MyObject
{
    static void Main(string[] args)
    {
        Person p1 = new Person();
        FileStream fstream = new FileStream("E:/tools/dotnet/hellowold/Solution1/ser.bin", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);

        BinaryFormatter binFormatterS = new BinaryFormatter();
        binFormatterS.Serialize(fstream,p1);

        BinaryFormatter binFormatterD = new BinaryFormatter();
        fstream.Position = 0;
        binFormatterD.Deserialize(fstream);
    }
}

运行结果如下

可以发现如果实现了ISerializable接口,那么序列化/反序列化流程则为:

序列化前:调用OnSerializing 特性的方法 序列化中: 调用GetObjectData 方法 序列化后:调用OnSerialized特性的方法 反序列化前:调用OnDeserializing特性的方法 反序列化中:调用有特定参数的构造方法 反序列化后:调用OnDeserialized特性的方法

代理选择器

再来看看实现代理选择器的类的序列化/反序列化流程

using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;

namespace ConsoleAppi1;
[Serializable]
public class Person:ISerializable
{

    public Person()
    {

    }
    public Person(SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("Constructor Begin!");
    }


    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("GetObjectData");
    }

    [OnDeserializing]
    private void TestOnDeserializing(StreamingContext sc)
    {
        Console.WriteLine("TestOnDeserializing");

    }
    [OnDeserialized]
    private void TestOnDeserialized(StreamingContext sc)
    {
        Console.WriteLine("TestOnDeserialized");
    }
    [OnSerializing]
    private void TestOnSerializing(StreamingContext sc)
    {
        Console.WriteLine("TestOnSerializing");
    }
    [OnSerialized]
    private void TestOnSerialized(StreamingContext sc)
    {
        Console.WriteLine("TestOnSerialized");
    }
}

//定义代理选择器,需要继承自ISerializationSurrogate,并实现GetObjectData,SetObjectData
public class MySerializationSurrogate : ISerializationSurrogate
{
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("GetObjectData of ISerializationSurrogate");
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        Console.WriteLine("SetObjectData of ISerializationSurrogate");
        return 1;
    }
}

public class MyObject
{
    static void Main(string[] args)
    {
        Person p1 = new Person();
        FileStream fstream = new FileStream("E:/tools/dotnet/hellowold/Solution1/ser.bin", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
        BinaryFormatter binFormatter = new BinaryFormatter();

        //设置代理选择器
        SurrogateSelector ss = new SurrogateSelector();
        ss.AddSurrogate(typeof(Person), binFormatter.Context, new MySerializationSurrogate());
        binFormatter.SurrogateSelector = ss;

        binFormatter.Serialize(fstream,p1);
        fstream.Position = 0;
        binFormatter.Deserialize(fstream);
    }
}

Ref:

https://github.com/Y4er/dotnet-deserialization