Item 6: distinguish between value types and reference types
Value Type or reference type? Struct or class? Which one should we use? This is not c ++. All types are defined as value types, and then we can create references for them. This is not Java, and all types are reference types. When we create a type, we must define its behavior, which is very important for us to select a value or reference it correctly. We must carefully review our decisions, because changes may cause errors in some inconspicuous places. When we create a type, declaring it as class or struct is a common problem. However, it is worthwhile to carefully consider this issue when I modify the code.
Choosing which one is not just as simple as liking it or not. The correct choice depends on how we expect the new type to function. Value types have no polymorphism and are more suitable for storing data in applications. The reference type has polymorphism and is suitable for defining the behavior patterns of applications. We should determine which type to choose by analyzing the role of the new type. Struct stores data and class-defined behavior.
In. NET and C #, We need to partition the value type and reference type. All parameters and return values in C ++ are carried out through the value type. It is very efficient to pass values, but there are also some problems: for example, partial copying is called the truncation effect (slicing problem )). When we use the object of a derived class as the base class object for transmission, only the part of the base class is passed, and part of the derived class is truncated.
In Java, all user-defined types are reference types, and all parameters and return values are passed through references. This processing policy is consistent, but sometimes it consumes some efficiency. Some types do not exist and there is no polymorphism. There will be excessive consumption when creating and destroying these types of instances. In C #, we can choose to use struct or class to determine whether to use the value type or reference type. The value type is lightweight and the reference type has polymorphism. The rational use of different types requires us to understand the difference between the value type and the reference type.
The method in the following example returns a mydata object.
Private mydata _ mydata;
Public mydata Foo ()
{
Return _ mydata;
}
Mydata v = Foo ();
Totalsum + = V. value;
If mydata is a value type, a copy of the value stored in V is returned. If it is a reference type, an internal Member's reference is returned, which also violates the class seal requirement.
Consider the following code:
Private mydata _ mydata;
Public mydata Foo ()
{
Return _ mydata. Clone () as mydata;
}
Mydata v = Foo ();
Totalsum + = V. value;
Now V is a copy of _ mydata. Two objects are created for the reference type, so we will not expose the internal members. The cost is to create an additional object. If V is a local variable, it will soon become effective, and the clone method needs to be used to test the type at runtime. These will reduce efficiency.
The type of data transmitted to the outside using public methods and properties should use the value type. However, this does not mean that all types returned from the public method and attribute should be value types. Assume that the mydata type in the above example contains some data, and its responsibility is to store the data.
But consider the following code:
Private mytype _ mytype;
Public imyinterface Foo ()
{
Return _ mytype as imyinterface;
}
Imyinterface IME = Foo ();
IME. dowork ();
The _ mytype variable is still returned from the foo method, but this time, instead of containing the data value, it calls the internal-defined method of the object through the definition interface. What we get this time is not mytype data but its behavior. These behaviors are manifested through the imyinterface interface, which will bring about great changes. For example, mytype should be a reference type rather than a value type, and should be responsible for displaying type behaviors rather than storing data.
These simple codes show us the difference between the value type and the reference type: the value type stores data and the reference type defines behavior. Now let's take a look at how they are stored in the memory. Consider the following classes
Public Class C
{
Private mytype _ A = new mytype ();
Private mytype _ B = new mytype ();
}
C Var = new C ();
How many objects have we created? How old are they? The answer depends on the type of mytype. If it is a value type, we allocate a memory space in the stack, which is twice the size of mytype. If it is a reference type, we need to allocate three pieces of memory space: one is a C object, 8 bytes, and the other two are mytype objects in the C object. The reason for this result is that the value type embeds the stored value in the type, but the reference type is not. Variables of each reference type only retain the reference, and the stored value is in another memory block.
It is important to select a value type or a reference type. Once a change occurs, it is not just as simple as replacing struct with a class. Consider the following code:
Public struct employee
{
Private string _ name;
Private int _ id;
Private decimal _ salary;
Public void pay (bankaccount B)
{
B. Balance + = _ salary;
}
}
This simple type includes a method for paying employees and working properly. Later, you decided to change it into a class because there are different employees: sales staff get commissions, and managers get bonuses.
Public class employee
{
Private string _ name;
Private int _ id;
Private decimal _ salary;
Public Virtual void pay (bankaccount B)
{
B. Balance + = _ salary;
}
}
This change will affect many places where you use struct. Where the original return value type is, the reference type is now returned. In the original place where the value type parameter is passed, the reference type parameter is now passed. This change will have completely different performance in the following code.
Employee e1 = employees. Find ("CEO ");
E1.salary + = bonus;
E1.pay (ceobankaccount );
It should be that the value is increased once, because the original value is now changed to reference. The compiler does not raise any objection, but this is a bug. We must note that this is not a simple replacement problem. This change also changes the behavior of the program.
The cause of the problem is that the employee type does not follow the value type rule. When defining an employee, in addition to storing data, it also adds the responsibility to pay the employee's salary. This is a type of responsibility that should use classes. Class can use polymorphism to flexibly complete these characters, but the structure is not. The structure should only be used to store data.
In the. NET Help document, we also mentioned that we should use the type size as one of the reasons for using the value or reference. In fact, this type of application is more important. For simple structures and data, the value type is the best choice. Indeed, value types are more efficient in memory management: less fragments, less garbage, and more direct. The most important thing is that when the return value type from methods and properties returns a copy of the original value, there is no risk of exposing internal members. Of course, it also has limitations. It cannot realize polymorphism and cannot inherit from value types, just as if they are sealed. We can use value types to implement interfaces, But boxing is required to reduce the efficiency. We should consider the value type as a simple storage container, rather than an object in OO.
We should use more reference types. There are several questions below. If the answer is yes, we should create a value type.
1. Is this type only responsible for data storage?
2. Is this type of public interface used to pass or modify its own data members?
3. Are you sure there are no child types for this type?
4. Are you sure this type does not require polymorphism?
Create a low-consumption value type to store data, and create a reference type class to define behavior for your application. If you are not sure what to use, use the reference type.
Translated from Objective C #: 50 specific ways to improve your C # by Bill Wagner
P.s. I have no idea about Java, so some explanations of Java in this article may not be in place.
Although it is simply used to store data, but if the structure is very complex, will it be more efficient to transfer the value than to transmit the reference? I'm afraid it will waste a lot of time dealing with the data in struct. In this case, is it better to transmit references? I am not very clear.
Although it may cause some trouble to use errors for transferring values and references, the errors of using a wrong value type are much larger than that of a reference type, if the value type is incorrectly set to the reference type, internal members are exposed, which reduces efficiency, but does not cause fatal injuries. However, once you find that the reference type is incorrectly defined as the value type, it is a lot of work. The limitation of the value type is too large, so it is better to use it with caution. As mentioned at the end of the book, the reference type is used without knowing what to use .....
The following is what msdn says:
Data types are separated into value type and reference type. The value type is either stack allocation or Inline allocation in the structure. The reference type is heap allocated. Both the reference type and value type are from the final base classObjectDerived. When the value type needs to act as an object, it allocates a package on the stack (this package makes the value type look like a referenced object) and copies the value of this value type to it. The package is marked so that the system knows that it contains a value type. This process is called packing, and its reverse process is called unboxing. Packing and unboxing can make any type process like an object.
Back to directory