Objective C # principle 27: Avoid using icloneable)

Source: Internet
Author: User
Tags protected constructor

Valid C # principle 27: Avoid using icloneable

Item 27: Avoid icloneable

Icloneable looks like a good idea: after implementing the icloneable interface for a type, you can support copying. If you do not want to support copying, do not implement it.
However, your object does not run in a "vacuum" environment, but it is best to support icloneable considering some influence on the derived class. 1. If a certain type supports icloneable, all derived classes must be consistent, that is, all Members must support the icloneable interface or provide a mechanism to support copying. Finally, objects that support deep copy may be copied if they contain objects with network structures during creation. Icloneable is aware of this problem, which is explained in its official definition: it supports both deep copy and shallow copy. A shallow copy creates a new object that copies all the member variables in the current object. If these member variables are of reference type, the new object and the source object contain the same reference. Deep copy can copy all member variables, and the reference type is also recursively copied. For built-in types such as integer, deep copy and shallow copy are the same. Which of the following types should be supported? This depends on the type itself. However, mixing deep copy and shallow copy in one type causes many inconsistencies. Once you are involved in the icloneable issue, it is hard to get rid of such a mix. Most of the time, we should avoid using icloneable to make the class simpler. This is much easier to use and implement.

Icloneable is not required for any value type that only uses the built-in type as a member. It is much more efficient to copy all values of the structure using a simple value assignment statement than to clone. The clone () method must bind the return type to forcibly convert it into a reference of system. object. The caller has to use forced conversion to retrieve the value from the box. I know that you have enough capabilities to do this, but do not replace the value assignment statement with the clone () function.

What happens when a value type contains a reference type? The most common case is that the value type contains a string:

Public struct errormessage
{
Private int errcode;
Private int details;
Private string MSG;

// Details elided
}

A string is a special case because it is a constant class. If you specify an error message string, all error message classes are referenced to the same string. This does not cause any problems, which is different from other general reference types. If you modify the MSG variable on any reference, you will recreate a String object for it (see Principle 7 ).

String is indeed a very interesting class, many c ++ProgramMembers do not understand this class, and some C # programs do not understand it, resulting in many inefficiency and even errors. A good understanding of the string class in C # (and the relationship between string and stringbulider) is helpful for learning C # well. Because this design concept can be applied to our own types .)

In general, if a structure contains an arbitrary reference type, the copy process is much more complicated. This is also rare. The built-in value assignment statement will make a shallow copy of the structure, so that the referenced variables in the two structures will be referenced to the same object. If you want to perform a deep copy, you must also copy the reference type, and you must know whether the reference type also supports using clone () for deep copy. In either case, you do not need to add support for icloneable to the value type. The value assignment statement creates a new copy of the value type.

A general value type: there is no reason to add support for the icloneable interface to a value type! Now let's look at the reference type. The icloneable interface should be supported for the reference type, so as to explicitly indicate whether it supports deep copy or shallow copy. The wise choice is to add support for icloneable, because it is clear that all derived classes must also support icloneable. Let's look at the simple inheritance relationship below:

Class basetype: icloneable
{
Private string _ label = "Class Name ";
Private int [] _ values = new int [10];

Public object clone ()
{
Basetype rval = new basetype ();
Rval. _ label = _ label;
For (INT I = 0; I <_ values. length; I ++)
Rval. _ values [I] = _ values [I];
Return rval;
}
}

Class derived: basetype
{
Private double [] _ dvalues = new double [10];

Static void main (string [] ARGs)
{
Derived d = new derived ();
Derived D2 = D. Clone () as derived;

If (d2 = NULL)
Console. writeline ("null ");
}
}

If you run this program, you will find that D2 is null. Although derived is derived from basetype, the clone () function inherited from the basetype class does not correctly support the derived class: It only copies the base class. Basetype. Clone () creates a basetype object, not a derived object. This is why D2 in the program is null rather than the derived object. Even if you have overcome this problem, basetype. Clone () cannot correctly copy the _ dvalues array defined in the derived class. Once you implement icloneable, you must enforce that all derived classes must implement it correctly. In fact, you should provide a hook function for all the derived classes to use your copy implementation (see Principle 21 ). During copying, the derived class can only copy the value type members or reference type members that implement the icloneable interface. This is a strict requirement for a derived class. Implementing the icloneable interface on the base class usually adds this burden to the derived class. Therefore, you should avoid implementing the icloneable interface in the sealing class.

Therefore, when the entire inheritance structure must implement icloneable, you can create an abstract clone () method and then force all the derived classes to implement it.

In this case, you need to define a method for the derived class to create a copy of the base class member. You can achieve this by defining a protected constructor:

Class basetype
{
Private string _ label;
Private int [] _ values;

Protected basetype ()
{
_ Label = "Class Name ";
_ Values = new int [10];
}

// Used by devived values to clone
Protected basetype (basetype right)
{
_ Label = right. _ label;
_ Values = right. _ values. Clone () as int [];
}
}

Sealed class derived: basetype, icloneable
{
Private double [] _ dvalues = new double [10];

Public derived ()
{
_ Dvalues = new double [10];
}

// Construct a copy
// Using the base class copy ctor
Private derived (derived right ):
Base (right)
{
_ Dvalues = right. _ dvalues. Clone ()
As double [];
}

Static void main (string [] ARGs)
{
Derived d = new derived ();
Derived D2 = D. Clone () as derived;
If (d2 = NULL)
Console. writeline ("null ");
}

Public object clone ()
{
Derived rval = new derived (this );
Return rval;
}
}

The base class does not implement the icloneable interface. By providing a protected constructor, the derived class can copy the members of the base class. The leaf class should be both sealed and necessary to implement the icloneable interface. The base class should not force all derived classes to implement the icloneable interface, but you should provide some necessary methods so that the derived classes that wish to implement the icloneable interface can be used.

The icloneable interface is useful, but we should avoid it relative to its rules. For the value type, you should not implement the icloneable interface and use the value assignment statement. For reference types, icloneable is supported on the leaf class only when the copy operation is necessary. When the base class may support icloneable, you should create a protected constructor. All in all, we should try to avoid using the icloneable interface.
======================================

Item 27: Avoid icloneable
Icloneable sounds like a good idea: you implement the icloneable interface for types that support copies. if you don't want to support copies, don't implement it. but your type does not live in a vacuum. your demo-to support icloneable affects derived types as well. once a type supports icloneable, all its derived types must do the same. all its member types must also support icloneable or have Some other mechanic to create a copy. finally, supporting deep copies is very problematic when you create designs that contain webs of objects. icloneable finesses this problem in its official definition: it supports either a deep or a shallow copy. A shallow copy creates a new object that contains copies of all member variables. if those member variables are reference types, the new object refe RS to the same object that the original does. A deep copy creates a new object that copies all member variables as well. all reference types are cloned recursively in the copy. in built-in types, such as integers, the deep and shallow copies produce the same results. which one does a type support? That depends on the type. but mixing shallow and deep copies in the same object causes quite a few inconsistencies. when you go wading into the icloneable waters, it can be hard to escape. most often, avoiding icloneable altogether makes a simpler class. it's easier to use, and it's easier to implement.

Any value type that contains only built-in types as members does not need to support icloneable; a simple assignment copies all the values of the struct more efficiently than clone (). clone () must box its return so that it can be coerced into a system. object reference. the caller must perform another cast to extract the value from the box. you 've got enough to do. don't write a clone () function that replicates assignment.

What about value types that contain reference types? The most obvious case is a value type that contains a string:

Public struct errormessage
{
Private int errcode;
Private int details;
Private string MSG;

// Details elided
}

 

String is a special case because this class is immutable. if you assign an error message object, both error message objects refer to the same string. this does not cause any of the problems that might happen with a general reference type. if you change the MSG variable through either reference, you create a new String object (see item 7 ).

The general case of creating a struct that contains arbitrary reference variables is more complicated. it's also far more rare. the built-in assignment for the struct creates a shallow copy, with both structs referring to the same object. to create a deep copy, you need to clone the contained reference type, and you need to know that the reference type supported a deep copy with its clone () method. in either way, you don't add support for icloneable to a value type; the assignment operator creates a new copy of any value type.

That covers value types: there is never a good reason to support the icloneable interface in value types. now let's move on to reference types. reference types shocould support the icloneable interface to indicate that they support either shallow or deep copying. you shoshould add support for icloneable judiciously because doing so mandates that all classes derived from your type must also support icloneable. consider this small hierarchy:

Class basetype: icloneable
{
Private string _ label = "Class Name ";
Private int [] _ values = new int [10];

Public object clone ()
{
Basetype rval = new basetype ();
Rval. _ label = _ label;
For (INT I = 0; I <_ values. length; I ++)
Rval. _ values [I] = _ values [I];
Return rval;
}
}

Class derived: basetype
{
Private double [] _ dvalues = new double [10];

Static void main (string [] ARGs)
{
Derived d = new derived ();
Derived D2 = D. Clone () as derived;

If (d2 = NULL)
Console. writeline ("null ");
}
}

 

If you run this program, you will find that the value of D2 is null. the derived class does inherit icloneable. clone () from basetype, but that implementation is not correct for the derived type: It only clones the base type. basetype. clone () creates a basetype object, not a derived object. that is why D2 is null in the test programit's not a derived object. however, even if you cocould overcome this problem, basetype. clone () cocould not properly copy the _ dvalues array that was defined in derived. when you implement icloneable, you force all derived classes to implement it as well. in fact, you shoshould provide a hook function to let all derived classes use your implementation (see item 21 ). to support cloning, derived classes can add only member variables that are value types or reference types that implement icloneable. that is a very stringent limitation on all derived classes. adding icloneable support to base classes usually creates such a burden on derived types that you shoshould avoid implementing icloneable in nonsealed classes.

When an entire hierarchy must implement icloneable, you can create an abstract clone () method and force all derived classes to implement it.

In those cases, you need to define a way for the derived classes to create copies of the base members. That's done by defining a protected copy constructor:

Class basetype
{
Private string _ label;
Private int [] _ values;

Protected basetype ()
{
_ Label = "Class Name ";
_ Values = new int [10];
}

// Used by devived values to clone
Protected basetype (basetype right)
{
_ Label = right. _ label;
_ Values = right. _ values. Clone () as int [];
}
}

Sealed class derived: basetype, icloneable
{
Private double [] _ dvalues = new double [10];

Public derived ()
{
_ Dvalues = new double [10];
}

// Construct a copy
// Using the base class copy ctor
Private derived (derived right ):
Base (right)
{
_ Dvalues = right. _ dvalues. Clone ()
As double [];
}

Static void main (string [] ARGs)
{
Derived d = new derived ();
Derived D2 = D. Clone () as derived;
If (d2 = NULL)
Console. writeline ("null ");
}

Public object clone ()
{
Derived rval = new derived (this );
Return rval;
}
}

 

Base classes do not implement icloneable; they provide a protected copy constructor that enables Derived classes to copy the base class parts. leaf classes, which shoshould all be sealed, implement icloneable when necessary. the base class does not force all derived classes to implement icloneable, but it provides the necessary methods for any Derived classes that want icloneable support.

Icloneable does have its use, but it is the exception rather than rule. you shoshould never add support for icloneable to value types; Use the assignment operation instead. you shoshould add support for icloneable to leaf classes when a copy operation is truly necessary for the type. base classes that are likely to be used where icloneable will be supported shocould create a protected copy constructor. in all other cases, avoid icloneable.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.