Objective C # Principle 17: minimum packing and unpacking
Item 17: Minimize boxing and unboxing
The value type is the data container, and they are not too many. On the other hand, the. NET Framework is designed as a single inherited reference type, system. object, which exists as a root object in the entire inheritance relationship. The purpose of designing these two types is completely different. the. NET Framework uses packing and unpacking to link two different types of data. Packing is to place a value type data on a non-type reference object, so that a value type can be used as a reference type as needed. In case of unpacking, an additional value type data is copied from the "box. Packing and unpacking allow you to use value-type data where you need to use the system. Object object. But the packing and unpacking operations are the performance robbers. In some cases, packing and unpacking will generate some temporary objects, which will causeProgramThere are some hidden bugs. Avoid packing and unpacking as much as possible.
Packing can convert a value type data into a reference type. A new reference object is created on the stack, which is the "box ", data of the Value Type stores a copy in this reference type. See Figure 2.3 to demonstrate how boxed objects are accessed and stored. The box contains a copy of the Value Type object, and the copy implements the interface of the packed object. When you want to retrieve any content from this box, a copy of the value type data will be created and returned. This is the key concept of packing and unpacking: one copy of the object is stored in the box, and the other copy will be created whenever you access the box again.
Figure 2.3 shows the value type data in the box. Converts a value type data into a reference of system. object, and an unknown reference type is created. Value-type data is stored in this unnamed reference object. All access methods must use this box to reach the place where value-type data is stored.
The most sinister thing is that the packing and unpacking are often completed automatically! When you use value-type data in any place where the expected type is system. object, the compiler will generate a boxing and unpacking statement. In addition, when you access value-type data through an interface pointer, packing and unpacking will also happen. You won't receive any warning when packing, even the simplest statement. For example:
Console. writeline ("a few numbers: {0}, {1}, {2 }",
25, 32, 50 );
To use the overloaded console. writeline function, you must reference an array of the system. Object type. The integer type is a value type. Therefore, you must bind it to the overloaded writeline method. The only way to force these three Integers to become system. Object objects is to pack them. In addition, within writeline, The tostring () method on the box object is called to reach the inside of the box. In a sense, you have generated the following structure:
Int I = 25;
Object o = I; // box
Console. writeline (O. tostring ());
In writeline, the following executionCode:
Object O;
Int I = (INT) O; // Unbox
String output = I. tostring ();
You may never write such code yourself, but let the compiler automatically convert from a specified type to system. Object. This is indeed what you did. The compiler just wants to help you. It wants you to succeed (call the function), and it is also happy to generate packing and unpacking statements for you as necessary to convert a value type data to system. object instance. To avoid such picky punishments, you should convert your type into a string instance before using them to call writeline.
Console. writeline ("a few numbers: {0}, {1}, {2 }",
25. tostring (), 32. tostring (), 50. tostring ());
Note: When you call the tostring method, you will still create a reference instance on the stack, but it does not need to be split because the object is already a reference type .)
This Code uses the known Integer type, and the value type is no longer implicitly converted to the system. Object type. This common example shows the first rule to avoid packing: note that it is implicitly converted to system. object. If this can be avoided, the value type should not be replaced by system. object.
Another common case is that when using the. NET 1. x set, you may accidentally convert a value type to the system. Object type. At any time, when you add a value type data to the set, you create a box. When you remove an object from the set at any time, you get a copy in the box. When getting an object from a box, you always need to create a copy. This will generate some hidden bugs in the application. The compiler will not help you find these bugs. This is the case of packing. Let's start to create a simple structure, which can modify one of the fields and put some of its instance objects in a collection:
Public struct person
{
Private string _ name;
Public string name
{
Get
{
Return _ name;
}
Set
{
_ Name = value;
}
}
Public override string tostring ()
{
Return _ name;
}
}
// Using the person in a collection:
Arraylist attendees = new arraylist ();
Person P = new person ("old name ");
Attendees. Add (P );
// Try to change the name:
// Wocould work if person was a reference type.
Person P2 = (person) attendees [0]);
P2.name = "new name ";
// Writes "old name ":
Console. writeline (
Attendees [0]. tostring ());
Person is a value type data that is boxed before being stored in arraylist. This will generate a copy. When the removed persone object modifies the access attribute, another copy is created. The modification you made is only for copying, But there is actually a third copy that uses the tostring () method to access the objects in attendees [0.
For this and other reasons, you should create some constant value types (see Principle 7 ). If you have to use a variable value type in the Set, use the system. array class, which is type-safe.
If an array is not a reasonable set, you can use the interface in C #1. X to correct this error. Try to select some interfaces instead of public methods to access the internal box to modify the data:
Public interface ipersonname
{
String name
{
Get; set;
}
}
Struct person: ipersonname
{
Private string _ name;
Public string name
{
Get
{
Return _ name;
}
Set
{
_ Name = value;
}
}
Public override string tostring ()
{
Return _ name;
}
}
// Using the person in a collection:
Arraylist attendees = new arraylist ();
Person P = new person ("old name ");
Attendees. Add (p); // box
// Try to change the name:
// Use the interface, not the type.
// No Unbox needed
(Ipersonname) attendees [0]). Name = "new name ";
// Writes "new name ":
Console. writeline (
Attendees [0]. tostring (); // Unbox
The boxed reference type will implement all the Implemented interfaces on the original data type. This means that you do not need to copy the data. You can call the ipersonaname. Name method on the box to directly access the value type data in the request box. The interface created on the Value Type allows you to access the inside of the box in the set and directly modify its value. The interface implemented on the value type does not make the value type multi-state, which introduces the penalty of packing (see principle 20 ).
In C #2.0, many restrictions have been modified (see Principle 49 ). Generic interfaces and generic sets handle the constructor and interface dilemma. Before that, we should avoid packing. Yes, the value type can be converted to system. object or any other interface reference. These conversions are implicit, making it complicated to discover them. These are environment and language rules. The packing and unpacking operations will inadvertently copy some objects, which will generate some bugs. Similarly, diversification of value types will cause performance loss. Always pay attention to those that convert the value type to system. object or interface type: place the value type in the set, and the call definition parameter is system. object Type method, or forcibly convert to system. object. Avoid it as much as possible!
====================================
Item 17: Minimize boxing and unboxing
value types are containers for data. they are not polymorphic types. on the other hand,. NET framework was designed with a single reference type, system. object, at the root of the entire object hierarchy. these two goals are at odds. the. net Framework uses boxing and unboxing to bridge the gap between these two goals. boxing places a value type in an untyped reference object to allow the value type to be used where a reference type is expected. unboxing extracts a copy of that value type from the box. boxing and unboxing are necessary for you to use value types where the system. object type is expected. but boxing and unboxing are always performance-robbing operations. sometimes, when boxing and unboxing also create temporary copies of objects, it can lead to subtle bugs in your programs. avoid boxing and unboxing when possible.
Boxing converts a value type to a reference type. A new reference object, the box, is allocated on the heap, and a copy of the value type is stored inside that reference object. see Figure 2.3 For an overview of how the boxed object is stored and accessed. the box contains the copy of the Value Type object and duplicates the interfaces implemented by the boxed value type. when you need to retrieve anything from the box, a copy of the value type gets created and returned. that's the key concept of boxing and unboxing: a copy of the object goes in the box, and another gets created whenever you access what's in the box.
Figure 2.3. value Type in a box. to convert a value type into a system. object Reference, an unnamed reference type is created. the value type is stored inline inside the unnamed reference type. all methods that access the value type are passed through the box to the stored value type.
The insidious problem with boxingand unboxing is that it happens automatically. the compiler generates the boxing and unboxing statements whenever you use a value type where a reference type, such as system. object is expected. in addition, the boxing and unboxing operations occur when you use a value type through an interface pointer. you get no warningsboxing just happens. even a simple statement such as this performs boxing:
Console. writeline ("a few numbers: {0}, {1}, {2 }",
25, 32, 50 );
The referenced overload of console. writeline takes an array of system. object references. ints are value types and must be boxed so that they can be passed to this overload of the writeline method. the only way to coerce the three integer arguments into system. object is to box them. in addition, inside writeline, code reaches inside the box to call the tostring () method of the object in the box. in a sense, you have generated this construct:
Int I = 25;
Object o = I; // box
Console. writeline (O. tostring ());
Inside writeline, the following code executes:
Object O;
Int I = (INT) O; // Unbox
String output = I. tostring ();
You wowould never write this code yourself. however, by leader the compiler automatically convert from a specific value type to system. object, you did let it happen. the compiler was just trying to help you. it wants you to succeed. it happily generates the boxing and unboxing statements necessary to convert any value type into an instance of system. object. to avoid this participant ular penalty, you shoshould convertyour types tostring instances yourself before you send them to writeline:
Console. writeline ("a few numbers: {0}, {1}, {2 }",
25. tostring (), 32. tostring (), 50. tostring ());
This Code uses the known type of integer, and value types (integers) are never implicitly converted to system. object. this common example extends strates the first rule to avoid boxing: Watch for implicit conversions to system. object. value types shoshould not be substituted for system. object if you can avoid it.
Another common case in which you might inadvertently substitute a value type for system. object is when you place value types in. net 1.x collections. this incarnation of. net Framework collections store references to system. object instances. anytime you add a value type to acollection, it goes in a box. anytime you remove an object from a collection, it gets copied from the box. taking an object out of the box always makes a copy. that introduces some subtle bugs in your application. the compiler does not help you find these bugs. it's all because of boxing. start with a simple structure that lets you modify one of its fields, and put some of those objects in a collection:
Public struct person
{
Private string _ name;
Public string name
{
Get
{
Return _ name;
}
Set
{
_ Name = value;
}
}
Public override string tostring ()
{
Return _ name;
}
}
// Using the person in a collection:
Arraylist attendees = new arraylist ();
Person P = new person ("old name ");
Attendees. Add (P );
// Try to change the name:
// Wocould work if person was a reference type.
Person P2 = (person) attendees [0]);
P2.name = "new name ";
// Writes "old name ":
Console. writeline (
Attendees [0]. tostring ());
Person is a value type; it gets placed in a box before being stored in the arraylist. that makes a copy. then another copy gets made when you remove the person object to access the name property to change. all you did was change the copy. in fact, a third copy was made to call the tostring () function through the attendees [0] object.
For this and other reasons, you shoshould create immutable value types (see item 7 ). if you must have a mutable value type in a collection, use the system. array class, which is type safe.
If an array is not the proper collection, you can fix this error in C # 1.x by using interfaces. by coding to interfaces rather than the type's public methods, you can reach inside the box to make the change to the values:
Public interface ipersonname
{
String name
{
Get; set;
}
}
Struct person: ipersonname
{
Private string _ name;
Public string name
{
Get
{
Return _ name;
}
Set
{
_ Name = value;
}
}
Public override string tostring ()
{
Return _ name;
}
}
// Using the person in a collection:
Arraylist attendees = new arraylist ();
Person P = new person ("old name ");
Attendees. Add (p); // box
// Try to change the name:
// Use the interface, not the type.
// No Unbox needed
(Ipersonname) attendees [0]). Name = "new name ";
// Writes "new name ":
Console. writeline (
Attendees [0]. tostring (); // Unbox
the box reference typeimplements all the interfaces implemented by the original object. that means no copy is made, but you call the ipersonname. name method on the box, which forwards the request to the boxed value type. creating interfaces on your value types enables you to reach inside the box to change the value stored in the collection. implementing an interface is not really treating a value type polymorphically, which reintroduces the boxing penalty (see item 20 ).
limitations of these limitations change with the introduction of generics in C #2.0 (see item 49 ). generic interfaces and generic collections will address the both the collection and the interface situations. until then, though, avoid boxing. yes, value types can be converted to system. object or any interface reference. that conversion happens implicitly, complicating the task of finding them. those are the rules of the environment and the language. the boxing and unboxing operations make copies where you might not found CT. that causes bugs. there is also a performance cost to treating value types polymorphically. be on the lookout for any constructs that convert value types to either system. object or interface types: placing values in collections, calling methods defined in system. object, and casts to system. object. avoid these whenever you can.