Effective C#之Item 25: Prefer Serializable Types

rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 25: Prefer Serializable Types

优先选择可序列化类型

Persistence is a core feature of a type. It's one of those basic elements that no one notices until you neglect to support it. If your type does not support serialization properly, you create more work for all developers who intend to use your types as a member or base class. When your type does not support serialization, they must work around it, adding their own implementation of a standard feature. It's unlikely that clients could properly implement serialization for your types without access to private details in your types. If you don't supply serialization, it's difficult or impossible for users of your class to add it.

持久性是一个类型的核心特征。它是直到你忽略了对其支持时才会注意的那些基本元素之一。如果你的类型没有很好的支持序列化,对于要将你的类作为成员或者基类使用的开发者来说,他们要做更多的工作。当你的类型不支持序列化的时候,他们必须围绕它做工作,为标准的特性添加自己的实现。如果不访问你的类型的内部私有细节的话,客户就不大可能恰当的为你的类型实现序列化。如果你不支持序列化,却要你的用户添加的话,是困难的或者不可能的。

Instead, prefer adding serialization to your types when practical. It should be practical for all types that do not represent UI widgets, windows, or forms. The extra perceived work is no excuse. .NET Serialization support is so simple that you don't have any reasonable excuse not to support it. In many cases, adding the Serializable attribute is enough:

相反,只要有实际意义,就要为你的类型添加序列化。对于不表示UI widgetswindows forms的所有类型来说都是实用的。.Net序列化支持很简单,使得你没有任何理由不支持它。在很多情况下,添加Serializable特性就足够了:

  1.     [Serializable]
  2.     public class MyType
  3.     {
  4.         private string label;
  5.         private int value;
  6.  }

Adding the Serializable attribute works because all the members of this type are serializable: string and int both support NET serialization. The reason it's important for you to support serialization wherever possible becomes obvious when you add another field of a custom type:

因为这个类型的所有成员(stringint)都是可序列化的,即都支持.Net序列化,所以添加Serializable特性就可以了。随处尽可能的支持序列化很重要,它的原因是在你向自定义类型添加另外的字段时就很明显了:

  1.     [Serializable]
  2.     public class MyType
  3.     {
  4.         private string label;
  5.         private int value;
  6.         private OtherClass otherObject;
  7. }

The Serializable attribute works here only if the OtherClass type supports .NET serialization. If OtherClass is not serializable, you get a runtime error and you have to write your own code to serialize MyType and the OtherClass object inside it. That's just not possible without extensive knowledge of the internals defined in OtherClass.

只有当OtherClass类型支持.Net序列化时,这里的Serializable特性才能工作。如果OtherClass不可序列化,你会得到一个运行时错误,你不得不编写自己的代码对MyType以及它内部的OtherClass对象进行序列化,如果不知道OtherClass内部的定义的话,这是不可能的。

.NET serialization saves all member variables in your object to the output stream. In addition, the .NET serialization code supports arbitrary object graphs: Even if you have circular references in your objects, the serialize and deserialize methods will save and restore each actual object only once. The .NET Serialization Framework also will recreate the web of references when the web of objects is deserialized. Any web of related objects that you have created is restored correctly when the object graph is deserialized. A last important note is that the Serializable attribute supports both binary and SOAP serialization. All the techniques in this item will support both serialization formats. But remember that this works only if all the types in an object graph support serialization. That's why it's important to support serialization in all your types. As soon as you leave out one class, you create a hole in the object graph that makes it harder for anyone using your types to support serialization easily. Before long, everyone is writing their own serialization code again.

.Net序列化将对象的所有成员变量保存为输出流。另外,.Net序列化代码支持任意对象图:甚至如果在你的对象内部有循环引用,序列化和反序列化方法也将会对每个实际的对象进行一次保存以及还原。当页面对象被反序列化的时候,.Net序列化框架也能重新建立页面引用。当对象图被反序列化的时候,任何你创建的相关对象的页面都会被准确的还原。最后一个重要的提示是,Serializable特性同时支持二进制和SOAP序列化。这一条款的技术将同时支持两种序列化格式。但是记住,只有当对象图里面的所有类型都支持序列化时,这才能工作。这就是为什么在你所有的类型里支持序列化很重要了。一旦你漏掉一个类,就在对象图中产生了一个漏洞,使得任何使用你的类型的人很难支持序列化。过不久,所有人都要再次重新编写他们的序列化代码。

Adding the Serializable attribute is the simplest technique to support serializable objects. But the simplest solution is not always the right solution. Sometimes, you do not want to serialize all the members of an object: Some members might exist only to cache the result of a lengthy operation. Other members might hold on to runtime resources that are needed only for in-memory operations. You can manage these possibilities using attributes as well. Attach the [NonSerialized] attribute to any of the data members that should not be saved as part of the object state. This marks them as nonserializable attributes:

添加Serializable特性是支持序列化对象最简单的技术。但是最简单的解决方式不总是正确的。有时,你不希望将对象的所有成员都序列化:一些成员的存在可能仅仅是为了缓存一个长操作的结果。其他一些成员可能持有了只有在活动内存操作中才需要的运行时资源。使用特性,你能够将管理这些可能性。向任何不需要作为对象状态的一部分保存下来的数据成员添加[NonSerialized]特性,使它们成为非序列化特性。

  1.     [Serializable]
  2.     public class MyType
  3.     {
  4.         private string label;
  5.         [NonSerialized]
  6.         private int cachedValue;
  7.         private OtherClass otherObject;
  8.  }

Nonserialized members add a little more work for you, you, the class designer. The serialization APIs do not initialize nonserialized members for you during the deserialization process. None of your types' constructors is called, so the member initializers are not executed, either. When you use the serializable attributes, nonserialized members get the default system-initialized value: 0 or null. When the default 0 initialization is not right, you need to implement the IDeserializationCallback interface to initialize these nonserializable members. IDeserializationCallback contains one method: OnDeserialization. The framework calls this method after the entire object graph has been deserialized. You use this method to initialize any nonserialized members in your object. Because the entire object graph has been read, you know that any function you might want to call on your object or any of its serialized members is safe. Unfortunately, it's not fool-proof. After the entire object graph has been read, the framework calls OnDeserialization on every object in the graph that supports the IDeserializationCallback interface. Any other objects in the object graph can call your object's public members when processing OnDeserialization. If they go first, your object's nonserialized members are null, or 0. Order is not guaranteed, so you must ensure that all your public methods handle the case in which nonserialized members have not been initialized.

非序列化成员为你(类的设计者)增加了一点点工作。在反序列化过程中,序列化API不为你初始化非序列化成员。你的类型的构造函数没有一个会被调用,因此成员初始化器也不会被执行。当你使用序列化特性时,非序列化成员获得它们默认的系统初始值:0或者null。当默认的0初始化不正确时,你需要实现IDeserializationCallback接口来初始化这些非序列化成员。IDeserializationCallback包含一个方法:OnDeserialization。在整个对象图被反序列化之后,框架调用该方法。你使用该方法来初始化对象里面的任何非序列化成员。因为整个对象图已经被读取了,所以你知道在你的对象里,想要去调用的任何方法或者它的任何序列化成员是安全的。不幸的是,这不是一个愚蠢的证明。在整个对象图已经被读取之后,框架调用对象图里面的每个支持IDeserializationCallback接口的对象的OnDeserialization方法。对象图里面的任何其它对象,在处理OnDeserialization时,能够调用你的对象的公共成员。如果它们先执行,你的对象的非序列化成员就是null或者0。顺序都不能被保证,因此你必须保证你的所有公共方法处理这个情况:非序列化成员还没有被初始化。

So far, you've learned about why you should add serialization to all your types: Nonserializable types cause more work when used in types that should be serialized. You've learned about the simplest serialization methods using attributes, including how to initialize nonserialized members.

因此,你已经学到了为何要向所有的类型添加序列化:当序列化类型在应该序列化的类型里面使用的时候,会引起更多的工作。你已经学到了最简单的序列化方法:使用特性,包括如何初始化非序列化成员。

Serialized data has a way of living on between versions of your program. Adding serialization to your types means that one day you will need to read an older version. The code generated by the Serializable attribute throws exceptions when it finds fields that have been added or removed from the object graph. When you find yourself ready to support multiple versions and you need more control over the serialization process, use the ISerializable interface. This interface defines the hooks for you to customize the serialization of your types. The methods and storage that the ISerializable interface uses are consistent with the methods and storage that the default serialization methods use. That means you can use the serialization attributes when you create a class. If it ever becomes necessary to provide your own extensions, you then add support for the ISerializable interface.

序列化数据有它自己的方式,可以存在于程序的各个版本之间。给你的类型添加序列化,意味着,有一天你将需要读取一个更老的版本。当发现有字段已经被添加到对象图或者从对象图移除后,由Serializable特性生成的代码会抛出异常。当你发现你自己准备好要支持多个版本时,你需要使用ISerializable接口对序列化进程进行更多的控制。接口为你定义好了钩子,以便于为自己的类型定义序列化。ISerializable接口使用的方法和存储,默认的序列化方法使用的方法和存储,是一致的。那意味着,当你创建一个类时,可以使用序列化特性。如果有一天需要提供自己的扩展时,就加入对ISerializable接口的支持。

As an example, consider how you would support MyType, version 2, when you add another field to your type. Simply adding a new field produces a new format that is incompatible with the previously stored versions on disk:

作为一个例子,当你向你的类型添加另外一个字段的时候,考虑该如何支持MyType版本2。简单的添加一个新的字段来生成新格式,这与前面存储在硬盘上的版本,是不兼容的。

 

  1.    [Serializable]
  2.     public class MyType
  3.     {
  4.         private string label;
  5.  
  6.         [NonSerialized]
  7.         private int value;
  8.  
  9.         private OtherClass otherObject;
  10.  
  11.         // Added in version 2
  12.         // The runtime throws Exceptions
  13.         // with it finds this field missing in version 1.0
  14.         // files.
  15.         private int value2;
  16. }

You add support for ISerializable to address this behavior. The ISerializable interface defines one method, but you have to implement two. ISerializable defines the GetObjectData() method that is used to write data to a stream. In addition, you must provide a serialization constructor to initialize the object from the stream:

添加对ISerializable的支持来表述这种行为。ISerializable接口定义了一个方法,但是你不得不实现2个。ISerializable定义了GetObjectData()方法,用来向一个流里面写入数据。另外,你必须提供序列化结构来从流里面初始化对象。

  1. private MyType(SerializationInfo info,StreamingContext cntxt);

The serialization constructor in the following class shows how to read a previous version of the type and read the current version consistently with the default implementation generated by adding the Serializable attribute:

下面类的序列化结构展示了如何通过添加Serializable特性生成的默认实现来达到读取到的类型的前面一个版本和当前版本是一致的。

  1. using System.Runtime.Serialization;
  2. using System.Security.Permissions;
  3.  
  4. [Serializable]
  5. public sealed class MyType : ISerializable
  6. {
  7.     private string label;
  8.  
  9.     [NonSerialized]
  10.     private int value;
  11.  
  12.     private OtherClass  otherObject;
  13.  
  14.     private const int DEFAULT_VALUE = 5;
  15.     private int  value2;
  16.  
  17.     // public constructors elided.
  18.  
  19.     // Private constructor used only by the Serialization framework.
  20.     private MyType( SerializationInfo info,StreamingContext cntxt )
  21.     {
  22.         label = info.GetString( "label" );
  23.         otherObject = (OtherClass)info.GetValue("otherObject"typeof(OtherClass));
  24.         try
  25.         {
  26.           value2 = info.GetInt32( "value2" );
  27.         }
  28.         catch ( SerializationException e )
  29.         {
  30.           // Found version 1.
  31.           value2 = DEFAULT_VALUE;
  32.         }
  33.     }
  34.  
  35.     [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter =true)]
  36.     void ISerializable.GetObjectData (SerializationInfo inf,StreamingContext cxt)
  37.     {
  38.         inf.AddValue( "label", label );
  39.         inf.AddValue( "otherObject ", otherObject );
  40.         inf.AddValue( "value2", value2 );
  41.     }
  42. }

The serialization stream stores each item as a key/value pair. The code generated from the attributes uses the variable name as the key for each value. When you add the ISerializable interface, you must match the key name and the order of the variables. The order is the order declared in the class. (By the way, this fact means that rearranging the order of variables in a class or renaming variables breaks the compatibility with files already created.)

序列化流将每个条款存储为一个key/value对。由特性生成的代码使用变量名作为每个值的key。当你添加ISerializable接口时,你必须匹配key的名字和变量的顺序。顺序就是在类里面生成的顺序。(顺便说一下,事实意味着在类里面重新安排变量的顺序或者重命名变量会打破与已经创建的文件之间的兼容性。)

Also, I have demanded the SerializationFormatter security permission. GetObjectData could be a security hole into your class if it is not properly protected. Malicious code could create a StreamingContext, get the values from an object using GetObjectData, serialize modified versions to another SerializationInfo, and reconstitute a modified object. It would allow a malicious developer to access the internal state of your object, modify it in the stream, and send the changes back to you. Demanding the SerializationFormatter permission seals this potential hole. It ensures that only properly trusted code can access this routine to get at the internal state of the object (see Item 47).

同样,我已经要求了SerializationFormatter的安全许可。如果没有恰当的进行保护的话,GetObjectData可能是你的类的一个安全漏洞。恶意代码可以生成一个StreamingContext,使用GetObjectData从一个对象得到它的值。将修改后的版本序列化给另外一个SerializationInfo,重建一个修改后的对象。这会允许恶意的开发者访问你的对象的内部状态,在流里面修改它,将发生的变化再送给你。要求SerializationFormatter许可封住了潜在的漏洞。这就可以保证恰当的信任代码可以访问子程序来获得对象的内部状态(Item 47)

But there's a downside to implementing the ISerializable interface. You can see that I made MyType sealed earlier. That forces it to be a leaf class. Implementing the ISerializable interface in a base class complicates serialization for all derived classes. Implementing ISerializable means that every derived class must create the protected constructor for deserialization. In addition, to support nonsealed classes, you need to create hooks in the GetObjectData method for derived classes to add their own data to the stream. The compiler does not catch either of these errors. The lack of a proper constructor causes the runtime to throw an exception when reading a derived object from a stream. The lack of a hook for GetObjectData() means that the data from the derived portion of the object never gets saved to the file. No errors are thrown. I'd like the recommendation to be "implement Serializable in leaf classes."

实现ISerializable接口,存在一个不好的方面。你可以看到在前面我将MyType类型定义成sealed。这会使得该类被强制成一个“叶子”类。在基类里面实现ISerializable接口使得所有的派生类实现序列化都很复杂。实现ISerializable意味着所有的派生类都应该为反序列化创建保护性的构造函数。另外,为了支持非封装类,你需要在GetObjectData方法里面为派生类创建钩子,使得它们可以向流里面加入自己的数据。编译器不捕捉任何这些错误。缺少GetObjectData()的钩子意味着,来自对象派生部分的数据从不会被保存到文件里。没有错误会被抛出。我更希望该建议是“在叶子类里面实现Serializable”。

I did not say that because that won't work. Your base classes must be serializable for the derived classes to be serializable. To modify MyType so that it can be a serializable base class, you change the serializable constructor to protected and create a virtual method that derived classes can override to store their data:

我没有那样说,因为那行不通。为了让派生类是serializable的,基类必须是serializable的。为了让MyType是可序列化的基类,必须对其进行修改,将序列化构造器修改为保护性的,创建虚方法,那样的话派生类可以重写来存储它们的数据。

  1. using System.Runtime.Serialization;
  2. using System.Security.Permissions;
  3.  
  4. [Serializable]
  5. public class MyType : ISerializable
  6. {
  7.     private string label;
  8.  
  9.     [NonSerialized]
  10.     private int value;
  11.  
  12.     private OtherClass  otherObject;
  13.  
  14.     private const int DEFAULT_VALUE = 5;
  15.     private int  value2;
  16.  
  17.     // public constructors elided.
  18.  
  19.     // Protected constructor used only by the Serialization framework.
  20.     protected MyType( SerializationInfo info,StreamingContext cntxt )
  21.     {
  22.         label = info.GetString( "label" );
  23.         otherObject = (OtherClass)info.GetValue("otherObject"typeof(OtherClass));
  24.         try
  25.         {
  26.           value2 = info.GetInt32( "value2" );
  27.         }
  28.         catch ( SerializationException e )
  29.         {
  30.           // Found version 1.
  31.           value2 = DEFAULT_VALUE;
  32.         }
  33.     }
  34.     [ SecurityPermissionAttribute( SecurityAction.Demand,SerializationFormatter =true ) ]
  35.     void ISerializable.GetObjectData(SerializationInfo inf,StreamingContext cxt )
  36.     {
  37.         inf.AddValue( "label", label );
  38.         inf.AddValue("otherObject", otherObject);
  39.         inf.AddValue( "value2", value2 );
  40.  
  41.         WriteObjectData( inf, cxt );
  42.     }
  43.  
  44.     // Overridden in derived classes to write
  45.     // derived class data:
  46.     protected virtual void WriteObjectData(SerializationInfo inf,StreamingContext cxt )
  47.     {
  48.     }
  49. }

A derived class would provide its own serialization constructor and override the WriteObjectData method:

派生类可以提供自己的序列化构造函数并且重写WriteObjectData方法:

  1.     public class DerivedType : MyType
  2.     {
  3.         private int DerivedVal;
  4.  
  5.         private DerivedType(SerializationInfo info,StreamingContext cntxt)
  6.             :base(info, cntxt)
  7.         {
  8.             DerivedVal = info.GetInt32("DerivedVal");
  9.         }
  10.  
  11.         protected override void WriteObjectData(SerializationInfo inf,StreamingContext cxt)
  12.         {
  13.             inf.AddValue("DerivedVal", DerivedVal);
  14.         }
  15.  }

The order of writing and retrieving values from the serialization stream must be consistent. I've chosen to read and write the base class values first because I believe it is simpler. If your read and write code does not serialize the entire hierarchy in the exact same order, your serialization code won't work.

向序列化流里面写入数值的顺序和从序列化流里面重写获得数值的顺序必须是一致的。我已经选择了首先为基类的值进行读和写,因为我相信这更简单。如果你不以完全相同的顺序来序列化整个体系,读和写这个代码,你的序列化代码将不能工作。

The .NET Framework provides a simple, standard algorithm for serializing your objects. If your type should be persisted, you should follow the standard implementation. If you don't support serialization in your types, other classes that use your type can't support serialization, either. Make it as easy as possible for clients of your class. Use the default methods when you can, and implement the ISerializable interface when the default attributes don't suffice.

为了序列化你的对象,.Net框架提供了简单的标准的算法。如果你的类型应该是持久的,就应该遵守标准的实现。如果在你的类型里面不支持序列化,使用你的类的其它类将也不能支持序列化。使你的类的客户尽可能的简单。当你可以的时候,使用默认的方法;当默认的特性不满足的时候,实现ISerializable接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值