Effective C#之17:Minimize Boxing and Unboxing.

来源:互联网 发布:c语言指针偏移 编辑:程序博客网 时间:2024/05/01 20:13

Item 17: Minimize Boxing andUnboxing

使装箱拆箱最少

Value types are containers fordata. They are not polymorphic types. On the other hand, the .NET Framework wasdesigned with a single reference type, System.Object, at the root of the entireobject hierarchy. These two goals are at odds. The .NET Framework uses boxingand unboxing to bridge the gap between these two goals. Boxing places a valuetype in an untyped reference object to allow the value type to be used where areference type is expected. Unboxing extracts a copy of that value type fromthe box. Boxing and unboxing are necessary for you to use value types where theSystem.Object type is expected. But boxing and unboxing are alwaysperformance-robbing operations. Sometimes, when boxing and unboxing also createtemporary copies of objects, it can lead to subtle bugs in your programs. Avoidboxing and unboxing when possible.

值类型是数据容器,不是多态类型。另一方面来,.Net框架被设计的时候,只有一个单独的引用类型System.Object在整个对象等级的根部。这2个目标是不一致的。在两个目标之间,.Net框架使用装箱和拆箱建立了一个管道。装箱,将一个值类型置于一个无类型的引用对象里,这样,在需要使用引用类型的地方,该值类型就能被使用了。拆箱则是从箱子里提取该值的一个副本。在要求使用System.Object类型的地方,装箱和拆箱使得值类型也可以使用。但是装箱和拆箱操作简直是掠夺资源。有时,装箱和拆箱也创建对象的临时副本,这会在程序里导致微小的bug。尽可能的避免装箱和拆箱操作。

Boxing converts a value type to areference type. A new reference object, the box, is allocated on the heap, anda copy of the value type is stored inside that reference object. See Figure2.3 for an illustration of how the boxed object is stored and accessed. Thebox contains the copy of the value type object and duplicates the interfacesimplemented by the boxed value type. When you need to retrieve anything fromthe box, a copy of the value type gets created and returned. That's the keyconcept of boxing and unboxing: A copy of the object goes in the box, andanother gets created whenever you access what's in the box.

装箱将一个值类型转换为引用类型。一个新的引用类型,即箱子,是在堆上面进行分配的,在引用类型的内部存储被装箱值类型的副本。图2.3展示了被装箱的对象如何被存储和访问。箱子包含有值类型的一个副本并且复制被装箱的值类型实现的接口。当你需要从一个箱子获取任何东西时,一个值类型的副本被创建并且被返回。这是装箱和拆箱操作的关键概念:对象的一个副本被放进箱子,当你访问箱子里面的东西的时候,另外一个副本又被创建了。

Figure2.3. Value type in a box. To convert a value type into a System.Objectreference, an unnamed reference type is created. The value type is storedinline inside the unnamed reference type. All methods that access the valuetype are passed through the box to the stored value type.

2.3 位于箱子中的值类型。为了将值类型转换成System.Object的引用,一个未命名的引用类型会被创建。该值类型被内联存储在这个未命名的类型内部。所有对值类型访问的方法,被通过箱子传递给被存储的值类型。


 

The insidious problem with boxing andunboxing is that it happens automatically. The compiler generates the boxingand unboxing statements whenever you use a value type where a reference type,such as System.Object is expected. In addition, the boxing and unboxingoperations occur when you use a value type through an interface pointer. Youget no warnings boxing just happens. Even a simple statement such as thisperforms boxing:

在装箱拆箱上,具有欺诈性的问题是:它是自动发生的。无论何时,你在要求使用引用类型(System.Object)的地方,使用值类型的时候,编译器生成装箱和拆箱语句。另外,当你通过一个接口指针使用值类型的时候,装箱和拆箱操作就会发生。装箱发生的时候,你得不到任何警示。甚至像这么一个简单的语句都执行装箱:

  1.     Console.WriteLine("A few numbers:{0}, {1}, {2}",25, 32, 50);

The referenced overload ofConsole.WriteLine takes an array of System.Object references. Ints are valuetypes and must be boxed so that they can be passed to this overload of theWriteLine method. The only way to coerce the three integer arguments intoSystem.Object is to box them. In addition, inside WriteLine, code reachesinside the box to call the ToString() method of the object in the box. In asense, you have generated this construct:

Console.WriteLine的引用重载采用一个System.Object引用的数组。Int是值类型,必须被装箱,那样的话才能被传给WriteLine方法的重载版本。唯一将3个整型参数强制转变成System.Object的方法就是对它们进行装箱。还有,在WriteLine里面,代码会深入到箱子内部调用箱子里的对象的ToString()方法。在某种意义上,你已经生成了下面的结构:

  1.             Int32 i =25;
  2.             Object o = i; // box
  3.             Console.WriteLine(o.ToString());

Inside WriteLine, the followingcode executes:

WriteLine里面,会执行下面代码:

  1.             Object o;
  2.             Int32  i = ( Int32 )o; // unbox
  3.             String output = i.ToString( );

You would never write this codeyourself. However, by letting the compiler automatically convert from aspecific value type to System.Object, you did let it happen. The compiler wasjust trying to help you. It wants you to succeed. It happily generates theboxing and unboxing statements necessary to convert any value type into aninstance of System.Object. To avoid this particular penalty, you should convertyour types to string instances yourself before you send them to WriteLine:

你可能从来没有自己写下这些代码。然而,通过利用编译器自动将一个特定的值类型转换成System.Object,确实是你让这些发生的(产生了这些代码)。编译器仅仅是试图帮助你,它希望你成功。它很高兴的生成必要的装箱和拆箱语句来将任何值类型转换成System.Object的实例。为了避免这个特殊的处罚,在将自己的类型发送给WriteLine前,应该自己将其转换成string实例。

  1.     Console.WriteLine("A few numbers:{0}, {1}, {2}",
  2.         25.ToString(), 32.ToString(), 50.ToString());

This code uses the known type ofinteger, and value types (integers) are never implicitly converted toSystem.Object. This common example illustrates the first rule to avoid boxing:Watch for implicit conversions to System.Object. Value types should not besubstituted for System. Object if you can avoid it.

这个代码使用了已知的整数类型,值类型(整数)从不会隐式的被转换成System.Object.这个常见的例子展示了避免装箱的第一个规则:注意向System.Object的隐式转换。如果你能避免的话,值类型不应该被替换成System.Object

Another common case in which youmight inadvertently substitute a value type for System.Object is when you placevalue types in .NET 1.x collections. This incarnation of the .NET Frameworkcollections store references to System.Object instances. Anytime you add avalue type to a collection, it goes in a box. Anytime you remove an object froma collection, it gets copied from the box. Taking an object out of the boxalways makes a copy. That introduces some subtle bugs in your application. Thecompiler does not help you find these bugs. It's all because of boxing. Startwith a simple structure that lets you modify one of its fields, and put some ofthose objects in a collection:

你可能不经意的将值类型转换成System.Object的另外一个常见的例子是:将值类型置于.Net1.x的集合中。这个具体化的.Net框架集合存储对System.Object实例的引用。任何时候,将值类型加入集合时,它都是被装箱的。任何时候,从集合里移除一个对象时,都是从箱子里面传出一个副本。从箱子里拿出一个对象总是会产生一副本。这会给你的应用程序引入一些微小的bug。编译器在寻找这些bug方面帮不了你,这全是因为装箱。有一个简单的结构体,可以允许你修改它的一个字段,将一些这样的对象放入集合中:

 

  1.    public struct Person
  2.     {
  3.         private String name;
  4.         public String Name
  5.         {
  6.             get
  7.             {
  8.                 return name;
  9.             }
  10.             set
  11.             {
  12.                 name = value;
  13.             }
  14.         }
  15.         public Person(String name)
  16.         {
  17.             this.name = name;
  18.         }
  19.         public override String ToString()
  20.         {
  21.             return name;
  22.         }
  23.   }
  24.      // Using the Person in a collection:
  25.     ArrayList attendees = new ArrayList();
  26.     Person p = new Person("Old Name");
  27.     attendees.Add(p);
  28.     // Try to change the name:
  29.     // Would work if Person was a reference type.
  30.     Person p2 = ((Person)attendees[0]);
  31.     p2.Name = "New Name";
  32.      // Writes "Old Name":
  33.     Console.WriteLine(attendees[0].ToString());

Person is a value type; it getsplaced in a box before being stored in the ArrayList. That makes a copy. Thenanother copy gets made when you remove the Person object to access the Nameproperty to change. All you did was change the copy. In fact, a third copy wasmade to call the ToString() function through the attendees[0] object.

Person是一个值类型,在它被存储在ArrayList之前被进行了装箱,这产生了一个副本。当你移除这个Person对象,访问它的Name属性并进行修改时,又产生了另外一个副本。所有你做得工作,都是改变了它的副本。事实上,通过attendees[0]对象调用ToString()方法时,产生了第三个副本。

For this and many other reasons,you should create immutable value types (see Item7). If you must have a mutable value type in a collection, use theSystem.Array class, which is type safe.

出于这个和很多其它原因,你应该创建具有不可变性的类型(Item7)。如果你必须在一个集合里面使用具有可变性的值类型,那么使用System.Array类型,它是类型安全的。

If an array is not the propercollection, you can fix this error in C# 1.x by using interfaces. By coding tointerfaces rather than the type's public methods, you can reach inside the boxto make the change to the values:

如果array不是合适的集合,在C#1.x里面,你可以使用接口来修复这个问题。通过对接口进行编码而不是对类型的公共方法编码,你可以深入到箱子内部,对它的值进行修改。

 

  1.    public interface IPersonName
  2.     {
  3.         string Name
  4.         {
  5.             get;
  6.             set;
  7.         }
  8.    }
  9.     struct Person : IPersonName
  10.     {
  11.         private string name;
  12.         public string Name
  13.         {
  14.             get
  15.             {
  16.                 return name;
  17.             }
  18.             set
  19.             {
  20.                 name = value;
  21.             }
  22.         }
  23.         public Person(String name)
  24.         {
  25.             this.name = name;
  26.         }
  27.         public override string ToString()
  28.         {
  29.             return name;
  30.         }
  31.    }
  32.  
  33.     // Using the Person in a collection:
  34.     ArrayList attendees = new ArrayList();
  35.     Person p = new Person("Old Name");
  36.     attendees.Add(p); // box
  37.      // Try to change the name:
  38.     // Use the interface, not the type.
  39.     // No Unbox needed
  40.     ((IPersonName)attendees[0]).Name = "New Name";
  41.     // Writes "New Name":
  42.     Console.WriteLine(attendees[0].ToString()); // unbox
  43.  

The box reference type implementsall the interfaces implemented by the original object. That means no copy ismade, but you call the IPersonName.Name method on the box, which forwards therequest to the boxed value type. Creating interfaces on your value typesenables you to reach inside the box to change the value stored in thecollection. Implementing an interface is not really treating a value typepolymorphically, which reintroduces the boxing penalty (see Item20).

“箱子”引用类型实现了所有原来对象实现的接口。这意味着,没有副本会被产生,但是你调用了箱子上的IPersonName.Name方法,它会将该请求推进给被装箱的值类型。在值类型上面创建接口,使你能够到达箱子的内部,修改存储在集合里面的值。实现接口并不是将值类型看成是多态性的,那样的话还是会重新引入装箱的带来的惩罚。(Item20)

Many of these limitations changewith the introduction of generics in C# 2.0 (see Item49). Generic interfaces and generic collections will address the both thecollection and the interface situations. Until then, though, avoid boxing. Yes,value types can be converted to System.Object or any interface reference. Thatconversion happens implicitly, complicating the task of finding them. Those arethe rules of the environment and the language. The boxing and unboxingoperations make copies where you might not expect. That causes bugs. There isalso a performance cost to treating value types polymorphically. Be on thelookout for any constructs that convert value types to either System.Object orinterface types: placing values in collections, calling methods defined inSystem.Object, and casts to System.Object. Avoid these whenever you can.

很多这些限制,在C#2.0引入泛型后,都有了改变。(Item 49)。泛型接口和泛型集合将会同时适应集合和接口的情况。即使到了那时,也要避免装箱。是的,值类型能够被转换成System.Object或者任何接口引用。这些转换都是隐式发生的,找到它们较复杂。这些是环境和语言的规则。装箱和拆箱操作会在你意想不到的地方产生副本。那会引起bug。像多态一样去对待值类型也会带来性能上的损失。对任何将值类型转换成System.Object或者接口的结构,要提高警惕:将值类型放入集合,调用在System.Object上定义的方法,强制转换成System.Object。任何时候,只要你能,就要避免。

原创粉丝点击