建设银行对账单查询网站营销型网站建设 合肥
- 作者: 五速梦信息网
- 时间: 2026年04月20日 10:43
当前位置: 首页 > news >正文
建设银行对账单查询网站,营销型网站建设 合肥,做商城网站的流程介绍,wordpress 会员注册插件序列化在C#中运用的非常多#xff0c;由于我是学Unity的#xff0c;Unity中的序列化特性也比较常用#xff0c;所以这篇文章我们讲讲序列化的用法。
在编码时#xff0c;我们的代码中的对象们都是保存在内存中的。
如果我们关闭一个软件#xff0c;那么里面的内容就随着…序列化在C#中运用的非常多由于我是学Unity的Unity中的序列化特性也比较常用所以这篇文章我们讲讲序列化的用法。
在编码时我们的代码中的对象们都是保存在内存中的。
如果我们关闭一个软件那么里面的内容就随着进程的消失而消失了。我们之前保存一个类可以使用反射来调用DLL库来生成一个这个类的对象。
但是如果说是代码中的对象的保存就是需要用序列化相关的API来对代码中的对象进行保存和读取。在C#中它的名字叫Serializeable关键字和它们的特性。同样的我们让一个对象序列化成了一个文件那么在代码中也要读取它所以与序列化相对应的就是反序列化。反序列化将一个数据流转化为一个文件。 序列化是将对象实例的状态存储到存储介质的过程而反序列化则是序列化的补集。在这个过程中 先将实例对象的公共字段和私有字段以及类的名称包括类所在的程序集转换为字节流。将字节流写入为数据流。反序列化时创建出与原对象完全相同的副本。 序列化的特点
1.持久存储
我们如果非常直白的将对象的字段保存到磁盘中并在使用时检索虽然不使用序列化也可以完成但是这种方法很容易出错尤其是目标对象的层次结构比较复杂的情况下。而在C#中序列化很好的简化了这样的工作。
C#中的对象有CLR来管理在内存中的分布dotNet框架通过使用反射提供自动的序列化机制。对象实例在序列化后类的名称、程序集、以及类的所有数据成员都被写入到存储媒体(二进制、XML、JSON)中。对象常常用成员变量来实现对其他实例的引用。
类序列化后CLR将跟踪所有已序列化的引用对象以确保同一对象不被序列化多次。
dotNet框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。
2.按值封送
一般说来一个实例对象仅在创建对象的应用程序域中有效。但是如果将一个对象的类标记为Serializeable。通过很简单的代码可以将该类自动序列化并可以从一个应用程序域传递到另外一个应用程序域然后再进行反序列化。
这样就可以在另外一个应用程序域中产生出该对象的一个精确副本。这样的过程称为按值封送。
Serializeable与NonSerialized
当我们要标记一个类可以序列化的时候最基本的方法是使用[Serializable]进行标记。
格式转换器
然后指定一段逻辑来进行序列化和反序列化操作。当我们指定序列化与反序列化的路径时要创建格式转换器来进行序列化在C#中可供选择的格式转换器为
二进制序列化 BinaryFormater对保存类型保真适用于不同的应用程序之间保留对象的状态。可以在不同程序之间共享对象。与下面的两个转换器不同的是它对于对象中的private字段也会进行序列化。XML和Soap序列化 SoapFormatter只会序列化公共属性和字段并且不会保留类型保真。适用于不限制读取程序的应用程序时非常有用。JSON序列化只序列化公共属性不会保留类型的保真。
由于XML和JSON是开放式标准所以用于在Web中数据共享是非常好的选择。
选择性序列化
我们的代码中并不是所有的字段都需要序列化我们如果需要有某个字段对象不进行序列化就可以使用[NonSerialized]关键字来“屏蔽”不要被序列化的字段。
我们在接下来的例子中都使用二进制序列化。
先写一个小例子我们定义一个类进行序列化里面有ID、姓名、性别并且设定Sex不能序列化 class Program{static void Main(){SerializeClassClassToSerialize serialClass new SerializeClassClassToSerialize();ClassToSerialize example1 new ClassToSerialize(){id 22,name xsy,Sex 男};serialClass.SerializeNow(example1);ClassToSerialize example2 serialClass.DeSerializeNow();Console.WriteLine(此时序列化后的ID为example2.id序列化后的名字为example2.name);if(example2.Sexnull){Console.WriteLine(此时Sex没有被序列化);}}}[Serializable]public class ClassToSerialize{public int id;public string name;[NonSerialized]public string Sex;}public class SerializeClassT{public void SerializeNow(T instance){FileStream stream new FileStream(C:\Users\熊思远\Desktop\temp.dat, FileMode.Create);BinaryFormatter binary new BinaryFormatter();binary.Serialize(stream, instance);stream.Dispose();stream.Close();}public T DeSerializeNow(){FileStream fileStream new FileStream(C:\Users\熊思远\Desktop\temp.dat, FileMode.Open, FileAccess.Read, FileShare.Read);BinaryFormatter b new BinaryFormatter();T example (T)b.Deserialize(fileStream);fileStream.Dispose();fileStream.Close();return example;}}
结果是显而易见的 C#中绝大部分的官方类型都可以被序列化我们可以从微软官方文档查看一个字段类型是否可以序列化。
序列化实现深拷贝
当我们在复制一个值类型字段的时候例如int ab那么b中的值将会复制到a中在复制之后a与b即毫无关系了这样复制之后完全克隆的情况称为深拷贝。
当我们复制一个引用类型字段的时候例如ExampleClass ab那么实际上a复制到b中的实际为a所指向的托管堆中的内存地址此时当我们修改a中的字段b中的字段也会随之改变这个在我们之前讲述值类型与引用类型的时候就讲过了。
这种复制对象仅仅复制一个指向同一个内存地址引用的操作称为浅拷贝。
那么我们如果要对引用类型进行深拷贝要如何操作呢序列化可以帮我们实现这个操作。
C#中专门有定义一个实现拷贝的接口ICloneable
使用Object的浅引用表进行浅拷贝
在C#的“老大哥”Object类中可以通过一个函数来返回该类的浅引用 我们在使用ICloneable实现Clone函数时这个时候可以这样书写 class Program{static void Main(){TestArray testArray1 new TestArray(){t111;array new int[3] { 44, 33, 22 },i aaa};TestArray testArray2 (TestArray)testArray1.Clone();testArray2.array[1] 456;testArray2.i bbb;t12345;Console.WriteLine(输出的原来的数组是);foreach (int t in testArray1.array){Console.Write(t.ToString() );}Console.WriteLine( );Console.WriteLine(testArray1.itestArray1.t);Console.WriteLine(此时的testArray1的哈希码是 testArray1.GetHashCode() 此时testArray2的哈希码是 testArray2.GetHashCode());}}[Serializable]class TestArray : ICloneable{public int t;public string i;public int[] array;public object Clone(){return this.MemberwiseClone();}}
此时我们输出testArray1的字段可以看出这里是 可以看出复制一个类对象使用Object.MemberwiseClone()时创建了一个新的实例来存放返回的Object类型的值二者哈希码不同对类中除了值类型和字符串类型以外的所有字段进行浅拷贝数组的值改变了而值类型t和字符串i都没有变化。
引用类型使用“”拷贝
这与我们直接进行TestArray testArray2 testArray1;是不一样的这样直接的“”引用复制会将值类型和字符串类型都指向同一个地址。我们将上面的调用克隆函数的指令改为直接等于 //TestArray testArray2 (TestArray)testArray1.Clone();TestArray testArray2 testArray1; 这样的效果就是 两个哈希码完全一致说明是指向了同一个内存地址。而类中的引用类型的值也发生了变化。
那么要在类中实现深拷贝可以使用序列化的手段进行深拷贝我们对上文中的例子进行改进 class Program{static void Main(){TestArray testArray1 new TestArray(){t 111,array new int[3] { 44, 33, 22 },i aaa};TestArray testArray2 (TestArray)testArray1.Clone();testArray2.array[1] 456;testArray2.i bbb;testArray2.t 12345;Console.WriteLine(输出的原来的数组是);foreach (int t in testArray1.array){Console.Write(t.ToString() );}Console.WriteLine( );Console.WriteLine(testArray1.i testArray1.t);Console.WriteLine(此时的testArray1的哈希码是 testArray1.GetHashCode() 此时testArray2的哈希码是 testArray2.GetHashCode());}}[Serializable]class TestArray : ICloneable{public int t;public string i;public int[] array;public object Clone(){return SerializeClassTestArray.getDeepClone(this);}}class SerializeClassT{public static T getDeepClone(T getInstance){using (MemoryStream stream new MemoryStream()){BinaryFormatter binary new BinaryFormatter();binary.Serialize(stream, getInstance);stream.Position 0;T returnInstance (T)binary.Deserialize(stream);return returnInstance;}}}
这样通过序列化一进一出就实现了一个类的深度拷贝在堆内存中生成了一个与原来的对象完全无关的副本这个时候我们看结果 二者的字段完全不同即使用了序列化实现了深拷贝的功能。
注意虽然引用类型在平时普通复制时都是浅拷贝但属于引用类型的字符串string在复制时和值类型的效果一致这个要当做一个特殊的情况看待。 在上文中使用了using语句对特定的非托管对象进行了生命周期的管理using语句使被管理对象(在上文中是MemoryStream)的生存周期只存在于花括号内当花括号内的逻辑执行完毕将对被管理的对象执行Dispose方法释放其内存。 自定义序列化 ISerializeable
序列化虽然好用但是使用它进行版本控制却是不容易例如当我们一个对象序列化保存了以后如果当时序列化前的某些值因为代码逻辑造成了修改那么需要精确控制执行序列化前和执行序列化后的逻辑操作为此C#中引入了ISerializeable。
ISerializeable接口中实现了一个GetObjectData方法为了形象一点我们把这个方法称为代理选取器 在GetObjectData方法中默认有两个参数
SerializationInfo将序列化时所有字段通过它的实例来保存保存的格式时类似于哈希表。通过关键字实现保存。
StreamingContext在创建格式化转换器时调用这个参数表明源和目标序列化数据。
我们尝试写一个序列化字典的方法 这个例子来源于Unity 的API文档我自己做了一点点修改 class Program{static void Main(){SerialClassDictionarySerialize serial new SerialClassDictionarySerialize();DictionarySerialize dicSet new DictionarySerialize();dicSet.myDictionary.Add(11, A);dicSet.myDictionary.Add(12, B);dicSet.myDictionary.Add(13, CD);serial.SerializeFunc(dicSet);DictionarySerialize dicGet serial.DeserializeFunc();foreach (var i in dicGet.myDictionary){Console.WriteLine(当前输出为 i.Key i.Value);}}}public class SerialClassT{public void SerializeFunc(T dicSet){BinaryFormatter binary new BinaryFormatter();FileStream stream new FileStream(C:\Users\熊思远\Desktop\\temp3.dat, FileMode.Create);binary.Serialize(stream, dicSet);stream.Close();}public T DeserializeFunc(){BinaryFormatter binary new BinaryFormatter();FileStream stream new FileStream(C:\Users\熊思远\Desktop\\temp3.dat, FileMode.Open, FileAccess.Read, FileShare.Read);T returnValue (T)binary.Deserialize(stream);stream.Dispose();return returnValue;}}[Serializable]public class DictionarySerialize : ISerializable{public Listint Savekeys new Listint();[NonSerialized]public Dictionaryint, string myDictionary new Dictionaryint, string();public DictionarySerialize(){ }public DictionarySerialize(SerializationInfo info, StreamingContext context){Savekeys (Listint)info.GetValue(SaveKeys, typeof(Listint));for (int t 0; t Savekeys.Count; t){myDictionary.Add(Savekeys[t], info.GetValue(Savekeys[t].ToString(), typeof(string)).ToString());}}public void GetObjectData(SerializationInfo info, StreamingContext context){foreach (KeyValuePairint,string i in myDictionary){Savekeys.Add(i.Key);}info.AddValue(SaveKeys, Savekeys);foreach (var i in Savekeys){info.AddValue(i.ToString(), myDictionary[i]);}}}
输出的结果为 当我们对类引入自定义序列化接口的时候该类对象在序列化的时候步骤如下
检查对象的类中是否有GetObjectData方法如果存在该方法再检查该方法是否处理指定需要序列化类型的对象。如果满足则将在序列化时调用GetObjectData方法。在反序列化时将会调用类中与GetObjectData参数列表一致的类构造函数以便将类中的字段送入新的对象实例中。
由于这里使用了ISerializable接口的原因即使我们使用了[Serializable]特性但每个字段的值不会为我们保存我们需要手动的进行每个需要序列化的值对SerializationInfo进行导入以此来保证在每个需要序列化的字段都进行了序列化。
我们使用ISerializable时既然会在序列化时调用所以也必须对应地设置一个反序列化时需要使用的构造函数为了使对象在通常情况下不会起冲突最好还在类中加入一个重载的构造函数。如果类中没有指定一个专用于自定义序列化的构造函数那么在反序列化结束的时候将会出现异常。
在使用自定义序列化时需要注意在继承链上的序列化产生的问题
对于类继承, 父类和子类都需要实现ISerializeable接口。而且在这种情况下, 派生类应在其GetObjectData中调用基类的实现即Base.GetObjectData()。 否则, 基类中的数据将不会序列化。
序列化中的其他可使用特性
[OnSerializing]应用OnSerializingAttribute特性的方法将会在序列化期间自动被调用。
[OnSerializied]应用OnSerializedAttribute特性的方法将会在序列化之后自动被调用。
[OnDeserializing]应用OnDeserializingAttribute特性的方法将会在被序列化期间自动被调用。
[OnDeserializied]应用OnDeserializedAttribute特性的方法将会在被序列化之后自动被调用。
注意以上四个特性都只能用于方法并且在类中表示应用特性的方法必须包含一个StreamingContext参数。
Unity中的序列化
在Unity中序列化是一个很重要的一环例如在Unity中有一个很便捷的功能在脚本中以Public形式标注的对象将会暴露在Inspector面板中这个里面就用到了序列化。
Unity中序列化除了C#的序列化功能以外还引入了[SerializeFiled]特性和[NonSerialized]特性。
在官方的注释中[SerializeFiled]的存在意义是强制序列化私有字段。
与之相对的[NonSerializeFiled]可以理解成强制不序列化公有字段。 需要注意的是我们不能把Unity中的序列化等同于C#中的序列化二者具有非常大的区别但是Unity中仍然可以使用C#的序列化方法进行字段的存取但是C#的序列化方式与Unity内置的序列化并不能看做一样的东西。
Unity序列化使用场景
保存和加载Asset和AssetBundle的保存和加载中对于从硬盘或内存中的对象的存取就用到了序列化这也包括API中的对象。仅在编辑器或播放测试模式下执行。
热重装当我们更改并保存脚本时Unity会及时的重新加载所有当前已加载的脚本数据。它首先将所有可序列化的变量存储在所有已加载的脚本中并在加载脚本后将其还原。这个过程Unity称为热重装。当热重装发生后所有的不可序列化的数据都会丢失。当脚本序列化后Unity将public的字段暴露在Inspector面板中显示。在显示字段的值的时候并不会与字段的脚本脚本进行通信。这一点很显而易见若一个脚本的公有的字段带有一个封装它的属性属性即使是public也不会暴露在Inspector中并且此时在面板中修改这个公有字段也是不会调用属性的。
PrefabUnity中的Prefab实际上是Unity经过序列化生成的资源文件当我们使用Instaniate函数在场景中克隆出一个预制体的时候实际上的过程是
将指定的GameObject所引用的游戏对象序列化得到序列化后的序列化流Stream。使用反序列化机制将序列化流反序列化生成一个新的游戏对象GameObject。 这样的方式即为我们在上文中讲到的使用序列化实现的深拷贝每次克隆的一个游戏对象都在一个新的内存空间中而不是一个引用地址中。这样能有效地防止生成多个对象后浅拷贝带来的修改数据和调试的复杂度。
Unity序列化要求
使用public或者使用了[SerializeField]特性。静态static、const、readonly来修饰的都不行。表示为可序列化的类型
其中可序列化的类型又有
自定义的、使用[Serializeable]修饰的非抽象类(引用类型)。自定义的、使用[Serializeable]修饰的结构体(值类型)。所有派生自UnityEngine.Object的类型。C#的基本元素类型int、float、double、bool、string等。元素类型为以上四种的Array和ListT。Unity中的一些内置类型Vector2Vector3Vector4RectQuaternionMatrix4x4ColorColor32LayerMaskAnimationCurveGradientRectOffsetGUIStyle等
Unity中的自定义序列化
同样的在Unity中遇到了不可序列化的类型我们同样需要自定义序列化在C#可以使用ISerializeable接口的基础上Unity官方也指定了ISerializationCallbackReceiver来在Unity中自定义序列化。 其中包含两个方法 OnAfterDeserialize实现此方法以在Unity反序列化对象后接收回调。 OnBeforeSerialize实现此方法以在Unity序列化对象之前接收回调。
例如在Unity中字典的元素是不能通过Unity序列化[SerializeField]来序列化的在C#中可以这个时候可以使用我们写一个Unity自定义序列化的例子
using System;
using System.Collections.Generic;
using UnityEngine;public class CSharpTest :MonoBehaviour,ISerializationCallbackReceiver
{public Listint keys new Listint { 3, 4, 5 };public Liststring values new Liststring { AA, BB, CC };public Dictionaryint, string myDictionary new Dictionaryint, string();void Start(){foreach(KeyValuePairint,string i in myDictionary){Debug.Log(i已经被序列化了 i.Value);}}public void OnBeforeSerialize(){keys.Clear();values.Clear();foreach (var kvp in myDictionary){keys.Add(kvp.Key);values.Add(kvp.Value);}Debug.Log(在序列化前被调用用于装载字典元素);}public void OnAfterDeserialize(){myDictionary new Dictionaryint, string();for (int i 0; i ! Math.Min(keys.Count, values.Count); i){myDictionary.Add(keys[i], values[i]);}Debug.Log(在序列化后被调用用于填充字典元素);}
}
我们运行游戏可以看到有如下情况 由于Unity中的C#脚本都是在定义脚本后随着脚本参数的变化而变化而每次变化Unity都会将脚本序列化以显示在Inspector面板上所以我们可以看到ISerializationCallbackReceiver的两个方法在游戏运行后被调用了多次。
参考文档
https://www.cnblogs.com/dazhong/archive/2007/04/09/705465.html
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/serialization/
https://docs.unity3d.com/Manual/script-Serialization.html
https://www.cnblogs.com/devhyj/p/4342592.html
- 上一篇: 建设银行电子银行网站微网站建设报价表
- 下一篇: 建设银行对账单查询网站最好的营销策划公司
相关文章
-
建设银行电子银行网站微网站建设报价表
建设银行电子银行网站微网站建设报价表
- 技术栈
- 2026年04月20日
-
建设银行的网站是多少什么APP可以做网站
建设银行的网站是多少什么APP可以做网站
- 技术栈
- 2026年04月20日
-
建设银行的官方网站前端 国外 网站
建设银行的官方网站前端 国外 网站
- 技术栈
- 2026年04月20日
-
建设银行对账单查询网站最好的营销策划公司
建设银行对账单查询网站最好的营销策划公司
- 技术栈
- 2026年04月20日
-
建设银行佛山分行网站小规模企业所得税税率
建设银行佛山分行网站小规模企业所得税税率
- 技术栈
- 2026年04月20日
-
建设银行高校缴费网站网站备案系统登录
建设银行高校缴费网站网站备案系统登录
- 技术栈
- 2026年04月20日
