https://boogipop.com/2024/02/02/%E3%80%90.NET%20%E5%AE%89%E5%85%A8%E3%80%91ASP.NET%20Deserialization%2001/#04-%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F
https://github.com/Y4er/dotnet-deserialization/blob/main/dotnet-serialize-101.md Y4er师傅三年前的文章了
这里简单将要 其实.Net主要就是C# 这么理解也行 .Net是参考java改过来的 其中SDK对应java的JDK
jvm对应clr,java se runtime对应 .net framework,java对应C#
(这个其中的namespace和php里的差不多 所以说学过php和java的再来学这个.Net的话 应该比较好入门)
这里用的是IDEA的Rider来写 没用VS 搭建环境就不写了
序列化 在java中,序列化和反序列化需要实现Serializable接口,在dotnet中则是使用特性
的方式进行标记Serializable。
1 2 3 4 5 6 7 [Serializable ] public class MyObject { public int n1; [NonSerialized ] public int n2; public String str; }
使用[NonSerialized]
来指定不可序列化的字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 using System;using System.IO;using System.Runtime.Serialization;using System.Runtime.Serialization.Formatters.Binary;namespace NetSerializer { [Serializable ] public class MyObject { public int n1; [NonSerialized ] public int n2; public String str; } class Program { public static void BinaryFormatterSerialize (string file, object o ) { BinaryFormatter binaryFormatter = new BinaryFormatter(); FileStream fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None); binaryFormatter.Serialize(fileStream, o); fileStream.Close(); Console.WriteLine($"serialize object {o} to file {file} ." ); } public static object BinaryFormatterDeserialFromFile (string file ) { IFormatter formatter = new BinaryFormatter(); Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); object o = formatter.Deserialize(stream); stream.Close(); return o; } static void Main (string [] args ) { try { MyObject myObject = new MyObject(); myObject.n1 = 1 ; myObject.n2 = 2 ; myObject.str = "jack" ; BinaryFormatterSerialize("1.bin" , myObject); MyObject myObject1 = (MyObject)BinaryFormatterDeserialFromFile("1.bin" ); Console.WriteLine($"n1:{myObject1.n1} " ); Console.WriteLine($"NonSerialized n2:{myObject1.n2} " ); Console.WriteLine($"str:{myObject1.str} " ); } catch (Exception e) { Console.WriteLine(e.Message); } Console.ReadKey(); } } }
反序列化结果
这个n2因为我们在代码中标注了这个 [NonSerialized]
字段 所以并没有进行反序列化 所以既然结果是0
在.Net中 用的是using 并不是import
这里的namespace相当于java的package 如果不用namespace{xxx} 这种格式的话 namespace xxx;这种格式的也行
这样写其实也行 都是一个意思 反序列化的结果也是一样的
可以看到对象除了被标记不能被序列化的字段以外全部恢复到了原来的值。查看生成的bin文件,发现序列化之后的数据采用0001 0000
开头
在上述我们给出的例子中 我们使用的是BinaryFormatter类 这个类表示使用二进制的形式进行序列化 在dotnet中还有其他序列化的类 每个序列化的类都对应了一种序列化的格式
BinaryFormatter 用于二进制格式
SoapFormatter 用于序列化soap格式
LosFormatter 用于序列化 Web 窗体页的视图状态
ObjectStateFormatter 用于序列化状态对象图
XmlSerializer 用于生成xml数据
JsonSerializer 用于生成Json数据
这些类都有个共同点 就是都实现了名为IFormatter、IRemotingFormatter的接口 IRemotingFormatter是用来远程调用的RPC接口 他也实现了IFormatter接口 所以重点看IFormatter接口就行了
在.Net中 这个 :
后面就是实现的接口 并且在其中这个sealed的意思是不可继承的意思 简单点就是说不能当父类
他里面的internal
参数就是说不能被这个类以外的类调用到 (里面的内部类是可以访问的)
这个里面的get和set的意思是 就是说这个属性可以被getter方法获取到也可以被setter方法来赋值
通过这三个字段,我们可以控制序列化和反序列化时数据的类型、值以及其他信息。
这三个属性的含义
类 字段名
含义用途
SerializationBinder Binder
用于控制在序列化和反序列化期间使用的实际类型
StreamingContext Context
序列化流上下文 其中states字段包含了序列化的来源和目的地
ISurrogateSelector SurrogateSelector
序列化代理选择器 接管formatter的序列化或反序列化处理
这个图是反序列化的流程图
当formatter调用Serialize方法的时候,会有以下的生命周期
首先确定formatter是否有代理选择器,如果有则检查代理选择器要处理的对象类型是否和给定的对象类型一致,如果一致,代理选择器会调用ISerializable.GetObjectData()
。
如果没有代理选择器,或者代理选择器不处理该对象类型,则检查对象是否有[Serializable]
特性。如果不能序列化则抛出异常。
检查该对象是否实现ISerializable接口,如果实现就调用其GetObjectData方法。
如果没实现ISerializable接口就使用默认的序列化策略,序列化所以没标记[NonSerialized]
的字段。
而在序列化和反序列化的过程中还有四个回调事件
01-代理器 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 using System;using System.IO;using System.Runtime.Serialization;using System.Runtime.Serialization.Formatters.Binary;using System.Security.Permissions;namespace ConsoleApplication1 { [Serializable ] public class MyObject : ISerializable { public string str { get ; set ; } public MyObject () { } protected MyObject (SerializationInfo info, StreamingContext context ) { Console.WriteLine("MyObject(SerializationInfo info, StreamingContext context)" ); str = info.GetString("str" ); } [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter) ] public virtual void GetObjectData (SerializationInfo info, StreamingContext context ) { Console.WriteLine("GetObjectData of MyObject.class" ); info.AddValue("str" , str, typeof (string )); } [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" ); } } class MySerializationSurrogate : ISerializationSurrogate { public void GetObjectData (object obj, SerializationInfo info, StreamingContext context ) { Console.WriteLine("GetObjectData of ISerializationSurrogate" ); info.AddValue("str" , ((MyObject)obj).str); } public object SetObjectData (object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector ) { Console.WriteLine("SetObjectData of ISerializationSurrogate" ); MyObject m = new MyObject(); m.str = (string )info.GetValue("str" , typeof (string )); return m; } } class Program { static void Main (string [] args ) { try { MyObject myObject = new MyObject(); myObject.str = "hello" ; using (MemoryStream memoryStream = new MemoryStream()) { BinaryFormatter binaryFormatter = new BinaryFormatter(); SurrogateSelector ss = new SurrogateSelector(); ss.AddSurrogate(typeof (MyObject), binaryFormatter.Context, new MySerializationSurrogate()); binaryFormatter.SurrogateSelector = ss; binaryFormatter.Serialize(memoryStream, myObject); memoryStream.Position = 0 ; myObject = null ; myObject = (MyObject)binaryFormatter.Deserialize(memoryStream); Console.WriteLine(myObject.str); } } catch (Exception e) { Console.WriteLine(e.StackTrace); } Console.ReadKey(); } } }
其实重点就在这个代理器(MySerializationSurrogate
)和这个序列化接口(ISerializable
) 这个序列化的生命周期与这两个有关
这是一个使用了SurrogateSelector代理选择器的序列化例子,输出如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 TestOnSerializing GetObjectData of ISerializationSurrogate TestOnSerialized TestOnDeserializing SetObjectData of ISerializationSurrogate TestOnDeserialized hello //------------------ OnDeserializing方法(序列化开始) 代理器的GetObjectData,获取属性 OnSerialized方法(序列化结束) OnDeserializing(反序列化开始) 代理器的SetObjectData,设置属性 OnDeserialized(反序列化结束)
02-非代理器和ISerializable模式 优先级是这个代理器 当代理器没有进行设置的时候 就会顺着去看ISerializable
模式
将这里注释掉 就是不设置代理器 其输出是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 TestOnSerializing GetObjectData of MyObject.class TestOnSerialized TestOnDeserializing MyObject(SerializationInfo info, StreamingContext context) TestOnDeserialized hello //---------------------- OnSerializing(序列化开始) 由于实现了ISerializable接口,因此会调用本身的GetObjectData方法 OnSerialized(序列化结束) OnDeserializing(反序列化开始) 类的实例化。protected MyObject(SerializationInfo info, StreamingContext context),在反序列化时由于实现了ISerializable接口,因此会调用构造方法,这个也是必须要加的,假如不加的话会报错 OnDeserialized(反序列化结束)
03-非代理器和非ISerializable模式 将ISerializable接口给除去 输出结果如下
1 2 3 4 5 6 TestOnSerializing TestOnSerialized TestOnDeserializing TestOnDeserialized hello
就是单纯的4个回调模式了
04-SerializationInfo MyObject的构造函数
1 2 3 4 5 6 //实现了ISerializable接口的类必须包含有序列化构造函数,否则会出错。 protected MyObject(SerializationInfo info, StreamingContext context) { Console.WriteLine("MyObject(SerializationInfo info, StreamingContext context)"); str = info.GetString("str"); }
跟进SerializationInfo这个类中
SerializationInfo info变量中表示序列化流的信息 ,对象的类型和值都存储在其中,查看类定义
可见其存储了对象类型、成员个数、程序集名称、类型名称等,还有一些AddValue的重载用于添加类实例字段变量键值对
(为什么我们要做这个构造函数? 里面给的东西其实和代理器的代码是差不多的 跟进这个我们构造的代理器)
在我们构造的代理器其中的Get和Set方法 其实这个Get对应的就是我们继承的ISerializable接口下的Get方法 然后这个Set对应就是我们反序列化类的构造函数 (这里就是为什么用这个SerializationInfo类的原因 里面的函数方法是类似的)
命令执行-01 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using System.Diagnostics; namespace ConsoleApplication1; public class Command { public static void Main(string[] args) { var start = Process.Start("cmd.exe", "/c calc"); // var process = new Process(); // process.StartInfo.FileName = "cmd.exe"; // process.StartInfo.Arguments = "/c calc"; // process.Start(); } }
类似java的runtime 在Start函数中 有两个关键的参数 就是这个FileName和Arguments
其运行的时候会先弹出cmd窗口 然后再执行calc 执行完后在关闭这个cmd窗口 并且这里也是没有回显的 接下来就解决这几个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using System; using System.Diagnostics; namespace ConsoleApplication1; public class Command { public static void Main(string[] args) { var process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.Arguments = "/c whoami"; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.Start(); var processStandardOutput = process.StandardOutput.ReadToEnd(); Console.WriteLine(processStandardOutput); } }
这样的话cmd窗口就不会弹出 回显也有了
其中RedirectStandardOutput和UseShellExecute需要注意一下
RedirectStandardOutput:
当设置为 true 时,表示重定向标准输出流,即将进程的标准输出流(通常是在控制台窗口中显示的信息)连接到 Process.StandardOutput 流中,以便你的程序可以读取进程的输出。
UseShellExecute:
当设置为 false 时,表示不使用操作系统的 shell 启动进程。相反,它允许你直接启动可执行文件,命令行等,而不需要借助 shell。(不弹出cmd窗口的关键)
命令执行-02 ObjectDataProvider 这个类也是可以进行命令执行的 其实最终也是调用到这个Process类才能执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using System;using System.Diagnostics;using System.Windows.Data;namespace WpfApplication2 ;public class Command { public static void Main (string [] args ) { var objectDataProvider = new ObjectDataProvider(); objectDataProvider.MethodName = "Start" ; objectDataProvider.MethodParameters.Add("cmd.exe" ); objectDataProvider.MethodParameters.Add("/c calc" ); objectDataProvider.ObjectInstance = new Process(); } }
创建项目的时候要选择wpf
接下来分析一下是怎么样能命令执行的
跟进ObjectDataProvider类ObjectInstance方法中
跟进Refresh方法中
跟进到这个QueryWorker方法中
在这个方法中 就会一直循环来给我们刚开始传入的值进行赋值
然后跟进这个方法中
这里就获取到了Process类了