现在的位置: 首页 > 综合 > 正文

Effective C#之Item 25: Prefer Serializable Types

2014年01月05日 ⁄ 综合 ⁄ 共 13764字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.