NET中的对象序列化(转)

完全复制一个引用类型对象主要有几种方法:

1.额外添加一个构造函数,入参为待复制对象(如果字段为引用类型,需要继续添加构造函数,这样情况会变的十分复杂。)

    public class Test1
    {
        private int field1;
        private int field2;
        private int field3;
        public Test1()
        { 

        }

        public Test1(Test1 test1)
        {
            this.field1 = test1.field1;
            this.field2 = test1.field2;
            this.field3 = test1.field3;
        }
    }

2.利用序列化反序列化(对性能会有杀伤)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Test t1 = new Test();
            Console.WriteLine(t1.list.Count);
            Test t2 = (Test)Clone(t1);
            t2.list.Add("");
            Console.WriteLine(t2.list.Count);
            Console.WriteLine(t1.list.Count);
            Console.ReadLine();
        }

        public static object Clone(object obj)
        {
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, obj);
            ms.Position = 0;
            return (bf.Deserialize(ms)); ;
        }
    }

    [Serializable]
    public class Test
    {
        public List<string> list = new List<string>();
    }
}

3.利用反射(测试了一个网上的接口可用,但是对性能杀伤和序列化反序列化相当,而且对代码混淆有一定影响。 
 

1.     
先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,

4、jQuery.extend()方法源码实现

jQuery的源码 – src/core.js
#L121源码及分析如下:

jQuery.extend = jQuery.fn.extend = function() { //给jQuery对象和jQuery原型对象都添加了extend扩展方法
  var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
  i = 1,
  length = arguments.length,
  deep = false;
  //以上其中的变量:options是一个缓存变量,用来缓存arguments[i],name是用来接收将要被扩展对象的key,src改变之前target对象上每个key对应的value。
  //copy传入对象上每个key对应的value,copyIsArray判定copy是否为一个数组,clone深拷贝中用来临时存对象或数组的src。

  // 处理深拷贝的情况
  if (typeof target === "boolean") {
    deep = target;
    target = arguments[1] || {};
    //跳过布尔值和目标 
    i++;
  }

  // 控制当target不是object或者function的情况
  if (typeof target !== "object" && !jQuery.isFunction(target)) {
    target = {};
  }

  // 当参数列表长度等于i的时候,扩展jQuery对象自身。
  if (length === i) {
    target = this; --i;
  }
  for (; i < length; i++) {
    if ((options = arguments[i]) != null) {
      // 扩展基础对象
      for (name in options) {
        src = target[name]; 
        copy = options[name];

        // 防止永无止境的循环,这里举个例子,如var i = {};i.a = i;$.extend(true,{},i);如果没有这个判断变成死循环了
        if (target === copy) {
          continue;
        }
        if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
          if (copyIsArray) {
            copyIsArray = false;
            clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是数组的话就让clone副本等于src否则等于空数组。
          } else {
            clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是对象的话就让clone副本等于src否则等于空数组。
          }
          // 递归拷贝
          target[name] = jQuery.extend(deep, clone, copy);
        } else if (copy !== undefined) {
          target[name] = copy; // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性。
        }
      }
    }
  }
  // 返回修改的对象
  return target;
};

jQuery的extend方法使用基本的递归思路实现了浅拷贝和深拷贝,但是这个方法也无法处理源对象内部循环引用,例如:

var a = {"name":"aaa"};
var b = {"name":"bbb"};
a.child = b;
b.parent = a;
$.extend(true,{},a);//直接报了栈溢出。Uncaught RangeError: Maximum call stack size exceeded

值类型直接存储对象,而引用类型存储对象的地址,在对引用类型进行复制的时候,也只是复制对象的地址。

以下各部分将探讨 .NET
框架提供的可靠的序列化机制,并着重介绍使您可以根据需要自定义序列化过程的一些重要功能。

3、深拷贝的实现

c#中的对象大体分为值类型和引用类型,值类型大致包括 int, string, struct等,引用类型大致包括 自定义Class,object 等。

四、        基本序列化

要使一个类可序列化,最简单的方法是使用 Serializable 属性[为什么webservice中没有标记Serializable?]

对它进行标记,如下所示:

[Serializable]

 public class MyObject {

  public int n1 = 0;

  public int n2 = 0;

  public String str = null;

}

以下代码片段说明了如何将此类的一个实例序列化为一个文件[我们不需要用手写这些代码吧?]:

MyObject obj = new MyObject();

obj.n1 = 1;

obj.n2 = 24;

obj.str = “一些字符串”;

IFormatter formatter = new BinaryFormatter();

Stream stream = new FileStream(“MyFile.bin”,
FileMode.Create,

FileAccess.Write, FileShare.None);

formatter.Serialize(stream, obj);

stream.Close();

本例使用二进制格式化程序进行序列化。

您只需创建一个要使用的流和格式化程序的实例,然后调用格式化程序的 Serialize
方法。流和要序列化的对象实例作为参数提供给此调用。

类中的所有成员变量(甚至标记为 private
的变量)都将被序列化,但这一点在本例中未明确体现出来。

在这一点上,二进制序列化不同于只序列化公共字段的 XML
序列化程序[共有多少种序列化?二进制序列化和xml序列化分别用在什么地方?二进制/XML是不是就是本文所说的存储媒体?]。

将对象还原到它以前的状态也非常容易。

首先,创建格式化程序和流以进行读取,

然后让格式化程序对对象进行反序列化。

以下代码片段说明了如何进行此操作。

IFormatter formatter = new BinaryFormatter();

Stream stream = new FileStream(“MyFile.bin”,
FileMode.Open,

FileAccess.Read, FileShare.Read);

MyObject obj = (MyObject)
formatter.Deserialize(fromStream);

stream.Close();

 

// 下面是证明

Console.WriteLine(“n1: {0}”, obj.n1);

Console.WriteLine(“n2: {0}”, obj.n2);

Console.WriteLine(“str: {0}”, obj.str);

上面所使用的 BinaryFormatter 效率很高,能生成非常紧凑的字节流。

所有使用此格式化程序序列化的对象也可使用它进行反序列化,对于序列化将在 .NET
平台上进行反序列化的对象,此格式化程序无疑是一个理想工具。

需要注意的是,对对象进行反序列化时并不调用构造函数[不调用构造函数,会不会为编程带来影响,例如不能在构造函数中添加逻辑?还是,不需要关心构造函数,因为对象的变量足以表明对象的状态?]。对反序列化添加这项约束,是出于性能方面的考虑[不调用构造函数?者对性能会有多大影响,这么说调用构造函数并不是完全没有必要?]。但是,这违反了对象编写者通常采用的一些运行时约定,因此,开发人员在将对象标记为可序列化时,应确保考虑了这一特殊约定。

如果要求具有可移植性,请使用 SoapFormatter。所要做的更改只是将以上代码中的格式化程序换成
SoapFormatter,而 SerializeDeserialize 调用不变。

对于上面使用的示例,该格式化程序将生成以下结果。

<SOAP-ENV:Envelope

 
xmlns:xsi=

  xmlns:xsd=””

  xmlns:SOAP-
ENC=

  xmlns:SOAP-
ENV=

  SOAP-ENV:encodingStyle=

 

  “

 
xmlns:a1=”;

 

  <SOAP-ENV:Body>

    <a1:MyObject id=”ref-1″>

      <n1>1</n1>

      <n2>24</n2>

      <str id=”ref-3″>一些字符串</str>

    </a1:MyObject>

  </SOAP-ENV:Body>

</SOAP-ENV:Envelope>

需要注意的是,无法继承 Serializable 属性。如果从 MyObject
派生出一个新的类,则这个新的类也必须使用该属性进行标记,否则将无法序列化。[如果Serializable
属性不能继承,为什么webservice类中没有设置Serializable 属性?]

例如,如果试图序列化以下类实例,将会显示一个 SerializationException,说明 MyStuff
类型未标记为可序列化。

public class MyStuff : MyObject

{

  public int n3;

}

使用序列化属性非常方便,但是它存在上述的一些限制。

有关何时标记类以进行序列化(因为类编译后就无法再序列化),请参考有关说明(请参阅下面的序列化规则)。

深度拷贝:重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型。两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

2.     
按值将对象从一个应用程序域发送至另一个应用程序域。

5、自己动手实现一个拷贝方法

var $ = (function () {
    'use strict';

    var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');

    function type () {
       return Object.prototype.toString.call(this).slice(8, -1);
    }

    for (var i = types.length; i--;) {
        $['is' + types[i]] = (function (self) {
            return function (elem) {
               return type.call(elem) === self;
            };
        })(types[i]);
    }

    return $;
})();//类型判断

function copy (obj,deep) { 
    if (obj === null || (typeof obj !== "object" && !$.isFunction(obj))) { 
        return obj; 
    } 

    if ($.isFunction(obj)) {
        return new Function("return " + obj.toString())();
    }
    else {
        var name, target = $.isArray(obj) ? [] : {}, value; 

        for (name in obj) { 
            value = obj[name]; 
            if (value === obj) {
                continue;
            }

            if (deep && ($.isArray(value) || $.isObject(value))) {
                target[name] = copy(value,deep);
            }
            else {
                target[name] = value;
            } 
        } 
        return target;
    }         
}

参考文章:

最重要的两个原因是:

3.2、JSON对象的parse和stringify

JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深拷贝。

//例1
var source = { name:"source", child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
target.name = "target";  //改变target的name属性
console.log(source.name); //source 
console.log(target.name); //target
target.child.name = "target child"; //改变target的child 
console.log(source.child.name); //child 
console.log(target.child.name); //target child
//例2
var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
//例3
var source = { name:function(){console.log(1);}, child:new RegExp("e") }
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
console.log(target.child); //Object {}

这种方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。还有一点不好的地方是它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。同时如果对象中存在循环引用的情况也无法正确处理。

1.     
将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;

浅度拷贝:复制一层对象的属性,并不包括对象里面的为引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。

 

1、javaScript的变量类型

(1)基本类型:
5种基本数据类型Undefined、Null、Boolean、Number 和
String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。

(2)引用类型:
存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

class=”wp_keywordlink”>JavaScript存储对象都是存地址的,所以浅拷贝会导致
obj1 和obj2
指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。

例如:数组拷贝

//浅拷贝,双向改变,指向同一片内存空间
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr1[0] = 'change';
console.log('shallow copy: ' + arr1 + " );   //shallow copy: change,2,3
console.log('shallow copy: ' + arr2 + " );   //shallow copy: change,2,3

九、        序列化规则

由于类编译后便无法序列化[序列化的时机是什么时候,由谁(CLR还是其它)来实现序列化?凡序列化的时机是什么时候,由谁来实现?那么,webservice是什么时候序列化,什么时候反序列化?][类编译后便无法再序列化?是不是类和对象都可以序列化?][序列化是不是和原数据表单联系很密切?],所以在设计新类时应考虑序列化。

需要考虑的问题有:

1.      是否必须跨应用程序域来发送此类?

2.      是否要远程使用此类?

3.      用户将如何使用此类?

也许他们会从我的类中派生出一个需要序列化的新类。只要有这种可能性,就应将类标记为可序列化。除下列情况以外,最好将所有类都标记为可序列化:

  • 所有的类都永远也不会跨越应用程序域。如果某个类不要求序列化但需要跨越应用程序域,请从
    MarshalByRefObject
    派生此类。
  • 类存储仅适用于其当前实例的特殊指针。例如,如果某个类包含非受控的内存或文件句柄,请确保将这些字段标记为
    NonSerialized
    或根本不序列化此类。
  • 某些数据成员包含敏感信息。在这种情况下,建议实现 ISerializable
    并仅序列化所要求的字段。

我的问题:

1:本文中的存储媒体是什么?xml?

2:序列化是针对对象的?那岂不是一个程序会需要很多的序列化?

3:什么是对象图表?

4:如何标记为Serializable?在类中,标记Serializable特性?

5:为什么webservice类中没有标记Serializable?

6:一般情况下,我们不需要用手写这些序列化,反序列化代码吧?

7:共有多少种序列化?二进制序列化和xml序列化分别用在什么地方?二进制/XML是不是就是本文所说的存储媒体?

8:不调用构造函数,会不会为编程带来影响,例如不能在构造函数中添加逻辑?还是,不需要关心构造函数,因为对象的变量足以表明对象的状态?

9:反序列化不调用构造函数?者对性能会有多大影响,这么说调用构造函数并不是完全没有必要?

10:如果Serializable
属性不能继承,为什么webservice类中没有设置Serializable 属性?

11:自定义序列化过程是什么,自己写代码序列化,反序列化对象?

12:反序列化的时候,不能调用构造函数吗?

13:对象被彻底重新构建,但是在反序列化过程中调用方法可能会带来不良的副作用,因为被调用的方法可能引用了在调用时尚未反序列化的对象引用[不明白什么意思?是当前对象,还是其它对象?]。

14:什么是散列表?

15:是不是序列化的对象中所引用的对象都必须是可序列化的对象,否则无法反序列化。那么,是不是webservice不能引用未序列化的对象?

16:序列化对象中含有NonSerialized标记,是不是有隐患?

17:序列化的时机是什么时候,由谁(CLR还是其它)来实现序列化?凡序列化的时机是什么时候,由谁来实现?那么,webservice是什么时候序列化,什么时候反序列化?

18:类编译后便无法再序列化?是不是类和对象都可以序列化?

19:序列化是不是和原数据表单联系很密切?

编者:一直没有理解变量按引用传递,最近在做一个项目,发现复制一个对象时,当改变对象新的对象时,原来的对象也发生了变化,原来对象中是按引用传递,它们都是保存在一个内存的同个位置,改变对象时,也改变了引用的地址。但我们往往需要拷贝的是一个新的对象,改变值同时也不改变原来的,这时就需要用到深度拷贝和浅度拷贝了。

在面向对象的环境中实现序列化机制时,必须在易用性和灵活性之间进行一些权衡。

2.2、Object.assign()

Object.assign()
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f);     // true

3.     
在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。

2.1、简单的引用复制

function shallowClone(copyObj) {
  var obj = {};
  for ( var i in copyObj) {
    obj[i] = copyObj[i];
  }
  return obj;
}
var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = shallowClone(x);
console.log(y.b.f === x.b.f);     // true

一、        简介

3.1、Array的slice和concat方法

Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里,是因为它看起来像是深拷贝。而实际上它是浅拷贝。原数组的元素会按照下述规则拷贝:

  • 如果该元素是个对象引用 (不是实际的对象),slice
    会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
  • 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean
    对象),slice
    会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

如果向两个数组任一中添加了新元素,则另一个不会受到影响。例子如下:

var array = [1,2,3]; 
var array_shallow = array; 
var array_concat = array.concat(); 
var array_slice = array.slice(0); 
console.log(array === array_shallow); //true 
console.log(array === array_slice); //false,“看起来”像深拷贝
console.log(array === array_concat); //false,“看起来”像深拷贝

可以看出,concat和slice返回的不同的数组实例,这与直接的引用复制是不同的。而从另一个例子可以看出Array的concat和slice并不是真正的深复制,数组中的对象元素(Object,Array等)只是复制了引用。如下:

var array = [1, [1,2,3], {name:"array"}]; 
var array_concat = array.concat();
var array_slice = array.slice(0);
array_concat[1][0] = 5;  //改变array_concat中数组元素的值 
console.log(array[1]); //[5,2,3] 
console.log(array_slice[1]); //[5,2,3] 
array_slice[2].name = "array_slice"; //改变array_slice中对象元素的值 
console.log(array[2].name); //array_slice
console.log(array_concat[2].name); //array_slice

为什么要使用序列化?

2、浅拷贝的实现

www.9778.com 1

八、        版本控制

.NET
框架支持版本控制和并排执行,并且,如果类的接口保持一致,所有类均可跨版本工作。

由于序列化涉及的是成员变量而非接口,所以,在向要跨版本序列化的类中添加成员变量,或从中删除变量时,应谨慎行事。特别是对于未实现
ISerializable
的类更应如此。

若当前版本的状态发生了任何变化(例如添加成员变量、更改变量类型或更改变量名称),都意味着如果同一类型的现有对象是使用早期版本进行序列化的,则无法成功对它们进行反序列化。

如果对象的状态需要在不同版本间发生改变,类的作者可以有两种选择:

  • 实现 ISerializable。这使您可以精确地控制序列化和反序列化过程,在反序列化过程中正确地添加和解释未来状态。
  • 使用 NonSerialized
    属性标记不重要的成员变量。仅当预计类在不同版本间的变化较小时,才可使用这个选项。例如,把一个新变量添加至类的较高版本后,可以将该变量标记为
    NonSerialized,以确保该类与早期版本保持兼容[序列化对象中含有NonSerialized标记,是不是有隐患?]。

浅拷贝和深拷贝的区分

首先深复制和浅复制只针对像 Object, Array
这样的复杂对象的。简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

抛开jQuery,上代码例子。下面是一个简单的浅复制实现:

var obj = { a:1, arr: [2,3] };
var shallowObj = shallowCopy(obj);

function shallowCopy(src) {
  var dst = {};
  for (var prop in src) {
    if (src.hasOwnProperty(prop)) {
      dst[prop] = src[prop];
    }
  }
  return dst;
}

因为浅复制只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript
存储对象都是存地址的,所以浅复制会导致 obj.arr 和 shallowObj.arr
指向同一块内存地址,大概的示意图如下。

www.9778.com 2

导致的结果就是:

shallowObj.arr[1] = 5;
obj.arr[1]   // = 5

而深复制则不同,它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。这就不会存在上面
obj 和 shallowObj 的 arr 属性指向同一个对象的问题。

var obj = { a:1, arr: [1,2] };
var obj2 = deepCopy(obj);

结果如下面的示意图所示:

www.9778.com 3

需要注意的是,如果对象比较大,层级也比较多,深复制会带来性能上的问题。在遇到需要采用深复制的场景时,可以考虑有没有其他替代的方案。在实际的应用场景中,也是浅复制更为常用。

本文简要介绍了 Microsoft .NET
中使用的序列化。

它还可用于按值将对象从一个应用程序域远程传递至另一个应用程序域。

例如,序列化可用于在 ASP.NET
中保存会话状态,以及将对象复制到 Windows
窗体的剪贴板中。

2.      然后再把字节流写入数据流。

三、        按值封送

对象仅在创建对象的应用程序域中有效。除非对象是从 MarshalByRefObject
派生得到或标记为 Serializable,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。

如果对象标记为 Serializable[如何标记为Serializable],则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送

如果对象是从 MarshalByRefObject
派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。

也可以将从 MarshalByRefObject 派生得到的对象标记为 Serializable

远程使用此对象时,负责进行序列化并已预先配置为 SurrogateSelector
的格式化程序将控制序列化过程,并用一个代理替换所有从 MarshalByRefObject
派生得到的对象。如果没有预先配置为 SurrogateSelector,序列化体系结构将遵从下面的标准序列化规则(请参阅序列化过程的步骤)。

七、        序列化过程的步骤

在格式化程序上调用 Serialize 方法时,对象序列化按照以下规则进行:

  • 检查格式化程序是否有代理选取器[是不是序列化的对象中所引用的对象都必须是可序列化的对象,否则无法反序列化。那么,是不是webservice不能引用未序列化的对象?]。如果有,检查代理选取器是否处理指定类型的对象。如果选取器处理此对象类型,将在代理选取器上调用
    ISerializable.GetObjectData
  • 如果没有代理选取器或有却不处理此类型,将检查是否使用 Serializable
    属性对对象进行标记。如果未标记,将会引发 SerializationException

  • 如果对象已被正确标记,将检查对象是否实现了 ISerializable。如果已实现,将在对象上调用
    GetObjectData

  • 如果对象未实现 Serializable,将使用默认的序列化策略,对所有未标记为
    NonSerialized
    的字段都进行序列化。

六、        自定义序列化

可以通过在对象上实现 ISerializable 接口来自定义序列化过程[自定义序列化过程是什么,自己写代码序列化,反序列化对象?]。

这一功能在反序列化后成员变量的值失效时尤其有用,但是需要为变量提供值以重建对象的完整状态。

要实现 ISerializable,需要实现 GetObjectData
方法以及一个特殊的构造函数[不是反序列化的时候,不调用构造函数吗?],在反序列化对象时要用到此构造函数。

以下代码示例说明了如何在前一部分中提到的 MyObject 类上实现
ISerializable

[Serializable]

public class MyObject : ISerializable

{

  public int n1;

  public int n2;

  public String str;

 

  public MyObject()

  {

  }

 

  protected MyObject(SerializationInfo info,
StreamingContext context)

  {

    n1 = info.GetInt32(“i”);

    n2 = info.GetInt32(“j”);

    str = info.GetString(“k”);

  }

 

  public virtual void GetObjectData(SerializationInfo
info,

StreamingContext context)

  {

    info.AddValue(“i”, n1);

    info.AddValue(“j”, n2);

    info.AddValue(“k”, str);

  }

}

在序列化过程中调用 GetObjectData 时,需要填充方法调用中提供的 SerializationInfo
对象。只需按名称/值对的形式添加将要序列化的变量。其名称可以是任何文本。只要已序列化的数据足以在反序列化过程中还原对象,便可以自由选择添加至
SerializationInfo
的成员变量。如果基对象实现了 ISerializable,则派生类应调用其基对象的 GetObjectData 方法。

需要强调的是,将 ISerializable 添加至某个类时,需要同时实现 GetObjectData
以及特殊的构造函数。如果缺少 GetObjectData,编译器将发出警告。但是,由于无法强制实现构造函数,所以,缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝试反序列化某个类,将会出现异常。

在消除潜在安全性和版本控制问题等方面,当前设计优于 SetObjectData
方法。例如,如果将 SetObjectData
方法定义为某个接口的一部分,则此方法必须是公共方法,这使得用户不得不编写代码来防止多次调用
SetObjectData
方法。可以想象,如果某个对象正在执行某些操作,而某个恶意应用程序却调用此对象的
SetObjectData
方法,将会引起一些潜在的麻烦。

在反序列化过程中,使用出于此目的而提供的构造函数将 SerializationInfo
传递给类。对象反序列化时,对构造函数的任何可见性约束都将被忽略,因此,可以将类标记为 public、protected、internal 或 private。

www.9778.com,一个不错的办法是,在类未封装的情况下,将构造函数标记为 protect。如果类已封装,则应标记为
private。要还原对象的状态,只需使用序列化时采用的名称,从 SerializationInfo
中检索变量的值。如果基类实现了 ISerializable,则应调用基类的构造函数,以使基础对象可以还原其变量。

如果从实现了 ISerializable
的类派生出一个新的类,则只要新的类中含有任何需要序列化的变量,就必须同时实现构造函数以及
GetObjectData
方法。

以下代码片段显示了如何使用上文所示的 MyObject
类来完成此操作。

[Serializable]

public class ObjectTwo : MyObject

{

  public int num;

 

  public ObjectTwo() : base()

  {

  }

 

  protected ObjectTwo(SerializationInfo si,
StreamingContext context) :

base(si,context)

  {

    num = si.GetInt32(“num”);

  }

 

  public override void
GetObjectData(SerializationInfo si,

StreamingContext context)

  {

    base.GetObjectData(si,context);

    si.AddValue(“num”, num);

  }

}

切记要在反序列化构造函数中调用基类,否则,将永远不会调用基类上的构造函数,并且在反序列化后也无法构建完整的对象。

对象被彻底重新构建,但是在反序列化过程中调用方法可能会带来不良的副作用,因为被调用的方法可能引用了在调用时尚未反序列化的对象引用[不明白什么意思?是当前对象,还是其它对象?]。

如果正在进行反序列化的类实现了 DeserializationCallback,则反序列化整个对象图表后,将自动调用
OnSerialization
方法。此时,引用的所有子对象均已完全还原。

有些类不使用上述事件侦听器,很难对它们进行反序列化,散列表[什么是散列表?]便是一个典型的例子。

在反序列化过程中检索关键字/值对非常容易,但是,由于无法保证从散列表派生出的类已反序列化,所以把这些对象添加回散列表时会出现一些问题。因此,建议目前不要在散列表上调用方法。

摘要:

序列化是指将对象实例的状态存储到存储媒体的过程。

在此过程中,

二、         持久存储

我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据。

尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。

可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。

序列化提供了轻松实现这个目标的快捷方法。

公共语言运行时 (CLR)
管理对象在内存中的分布,.NET
框架则通过使用反射提供自动的序列化机制。

对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体[本文中的存储媒体是什么?xml?]中。

对象通常用成员变量来存储对其他实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次[序列化是针对对象的?那岂不是一个程序会需要很多的序列化?]。

.NET
框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。

对对象图表[什么是对象图表?]的唯一要求是,由正在进行序列化的对象所引用的所有对象都必须标记为
Serializable(请参阅基本序列化)。否则,当序列化程序试图序列化未标记的对象时将会出现异常。

当反序列化已序列化的类时,将重新创建该类,并自动还原所有数据成员的值。

只要您对此过程有足够的控制能力,就可以使该过程在很大程度上自动进行。例如,简单的二进制序列化不能满足需要,或者,由于特定原因需要确定类中那些字段需要序列化。

五、        选择性序列化

类通常包含不应被序列化的字段。例如,假设某个类用一个成员变量来存储线程 ID。当此类被反序列化时,序列化此类时所存储的 ID
对应的线程可能不再运行,所以对这个值进行序列化没有意义。

可以通过使用 NonSerialized 属性标记成员变量来防止它们被序列化,如下所示:

[Serializable]

public class MyObject

{

  public int n1;

  [NonSerialized] public int n2;

  public String str;

}