Valid tive C # Principle 7: select a constant atomic value type data

Source: Internet
Author: User

Immutable types is actually very simple. They are created and their values are fixed. If you verify some parameters that are used to create an object, from the previous point of view, you know that it is in a valid (valid) state. You cannot modify the internal status of an object to make it invalid. After an object is created, you must carefully protect the object. Otherwise, you have to perform error verification to prohibit any state changes. A constant type is inherently thread-complete: Multiple visitors can access the same content at the same time. If the internal status cannot be modified, different threads cannot be given the opportunity to view inconsistent data views. Constant types can be safely exposed from your class. The caller cannot modify the internal status of an object. Constant types can work well on hash-based code sets. The value returned by the object. gethashcode () method must be the same for the same instance (see Principle 10), and this is where the constant type can always succeed.

Not all types can be constant. If you can, you need to clone an object to modify the status of any program. This is why constant and atomic data are recommended at the same time. Break down your object into a natural single entity structure. An address is a simple task. It consists of multiple related fields. Changing one of the fields may mean modifying other fields. A customer type is not an atomic type. A customer type may contain many small information blocks: addresses, names, and one or more phone numbers. Any unrelated information block can be changed. A customer may change the phone number without moving. Another customer may keep the original phone number when moving the house. It is also possible that a customer has changed his or her name, and has not changed his or her phone number without moving the house. A customer type is not an atomic type. It consists of multiple constant components: addresses, names, and a set of paired phone numbers. The atomic type is a single entity: You naturally Replace the object content with the atomic type. This exception changes one of its composition fields.

The following is the implementation of a typical variable Address class:

// Mutable Address structure.public struct Address{    private string  _line1;    private string _line2;    private string  _city;    private string _state;    private int    _zipCode;    // Rely on the default system-generated    // constructor.    public string Line1    {        get { return _line1; }        set { _line1 = value; }    }    public string Line2    {        get { return _line2; }        set { _line2 = value; }    }    public string City    {        get { return _city; }        set { _city= value; }    }    public string State    {        get { return _state; }        set        {            ValidateState(value);            _state = value;        }    }    public int ZipCode    {        get { return _zipCode; }        set        {            ValidateZip( value );            _zipCode = value;        }    }  // other details omitted.}// Example usage:Address a1 = new Address( );a1.Line1 = "111 S. Main";a1.City = "Anytown";a1.State = "IL";a1.ZipCode = 61111 ;// Modify:a1.City = "Ann Arbor"; // Zip, State invalid now.a1.ZipCode = 48103; // State still invalid now.a1.State = "MI"; // Now fine.

Changing the internal state means that it is likely to violate the immutability of the object, at least temporary. When you change the city field, you make A1 invalid. The city changes so that it does not match the continent field and the area code field. Code damage does not seem fatal, but it is only a small part of a multi-threaded program. After the city changes, any content changes before the continent changes will potentially enable another thread to see a conflicting data view.

Okay, so you are not going to write multi-threaded programs. You are still in trouble. As you can imagine, the zone code is invalid and an exception is thrown. You have done something you want to do, but you have put the system in an invalid state. To solve this problem, you need to add a large internal verification code in the Address class. This verification code requires a lot of space and is complex. To fully implement the expected security, when you modify multiple fields, you need to create a passive data copy around your code block. Thread security may require the addition of a clear thread synchronization tool for detecting each attribute, including set and get. All in all, this will be a significant action-and it is likely to be overly scalable when you add new features.

Replace the address structure with a constant type. Start to change all fields to read-only ones:

public struct Address{    private readonly string _line1;    private readonly string _line2;    private readonly string _city;    private readonly string _state;    private readonly int _zipCode;    // remaining details elided}

You also need to remove all attribute settings:

public struct Address{    // ...    public string Line1    {        get { return _line1; }    }    public string Line2    {        get { return _line2; }    }    public string City    {        get { return _city; }    }    public string State    {        get { return _state; }    }    public int ZipCode    {        get { return _zipCode; }    }}

Now you have a constant type. To make it work effectively, you must add a constructor to fully initialize the address structure. This address structure only requires an additional constructor to verify each field. A copy constructor is not required because the assignment operator is still efficient. Remember, the default constructor is still accessible. This is an address structure where all the default strings are null and the zip code is 0:

public struct Address{    private readonly string _line1;    private readonly string _line2;    private readonly string _city;    private readonly string _state;    private readonly int _zipCode;    public Address(string line1,      string line2,      string city,      string state,      int zipCode)    {        _line1 = line1;        _line2 = line2;        _city = city;        _state = state;        _zipCode = zipCode;        ValidateState(state);        ValidateZip(zipCode);    }    // etc.}

When using this constant data type, you must directly use different calls to modify its status. You would rather create a new object than modify an instance:

// Create an address:Address a1 = new Address( "111 S. Main",  "", "Anytown", "IL", 61111 );// To change, re-initialize:a1 = new Address( a1.Line1,  a1.Line2, "Ann Arbor", "MI", 48103 );

The value of A1 is one of the two: its original location is anytown, or Ann Arbor, which is later updated. As in the previous example, you no longer need to temporarily invalidate an object to modify an existing address. Here, only some temporary states exist during the execution of the constructor, but internal states cannot be accessed outside the constructor. Soon, a new address object will soon be generated, and its value will remain fixed. This is the expected security: A1 is either the default original value or a new value. If an exception occurs when constructing an object, A1 retains the original default value.

Why is an exception during construction not affecting the A1 value? As long as the constructor does not return a correct result, A1. Because it is a value assignment statement. This is why the constructor is used to update objects, rather than adding another function to update objects, because even if a function is used to update objects, it is possible to update objects in half, if an exception occurs, the object is in an incorrect state. You can refer to the date and time structure in. net. It is a typical constant example. It does not provide any methods to modify individual year, month, day, or week. If you modify one of them separately, the entire date may be in an incorrect state: for example, you change the date to the 31st, but it is likely that the month does not have the 31st, and the week may be different. It also does not provide any method to set the parameters at the same time. After reading the rule, you will understand why. Refer to the datetime structure to better understand why constant types are used. Note: Some books translate immutable type into immutable type .)

To create a constant type, make sure that your users have no chance to modify the internal status..The value type does not support derived classes.So you don't have to define a worry about the derived class to modify its internal state. But you need to pay attention to any variable reference type field within the constant type. After you implement constructor for these types, you need to passively copy the variable reference type again (in this article, it should be to protect data, A copy that has to be performed during data assignment, so it is regarded as a "defensive" Copy. Passive copy indicates that the copy is not spontaneous, but cannot ).

In all these examples, we assume that phone is a constant value type, because we only involve the constant of the Value Type:

// Almost immutable: there are holes that would// allow state changes.public struct PhoneList{    private readonly Phone[] _phones;    public PhoneList( Phone[] ph )    {            _phones = ph;    }    public IEnumerator Phones    {        get        {             return _phones.GetEnumerator();        }    }}Phone[] phones = new Phone[10];// initialize phonesPhoneList pl = new PhoneList( phones );// Modify the phone list:// also modifies the internals of the (supposedly)// immutable object.phones[5] = Phone.GeneratePhoneNumber( );

This array is a reference type. The array referenced in phonelist references the array bucket allocated outside the object. Developers can modify your constant structure by referencing another object to the bucket. To avoid this possibility, you need to make a passive copy of the array. The previous example shows the drawbacks of a variable set. If the telephone type is a variable reference type, more hazards may exist. The customer can modify the value of the set, even if the set is protected and not modified by anyone. This passive copy should be implemented in each constructor, regardless of whether there are referenced objects in your constant type:

// Immutable: A copy is made at construction.public struct PhoneList{    private readonly Phone[] _phones;    public PhoneList( Phone[] ph )    {        _phones = new Phone[ ph.Length ];        // Copies values because Phone is a value type.        ph.CopyTo( _phones, 0 );    }    public IEnumerator Phones    {        get        {            return _phones.GetEnumerator();        }    }}Phone[] phones = new Phone[10];// initialize phonesPhoneList pl = new PhoneList( phones );// Modify the phone list:// Does not modify the copy in pl.phones[5] = Phone.GeneratePhoneNumber( );

This principle should also be followed when you return a variable type reference. If you add an attribute to retrieve the entire array linked list from the phonelist structure, this accesser must also implement a passive copy. For details, see Principle 23.

This complex type indicates three policies that you should use when initializing your constant object. This address structure defines a constructor so that your customers can initialize an address. Defining a reasonable constructor is usually the easiest to achieve.

You can also create a factory method to implement a structure. The factory makes it easier to create a common value type data .. The color type of the. NET Framework is subject to this policy to initialize the system color. This static method uses color. fromknowncolor () and color. fromname () to copy a given system color from the currently displayed color and return it to the user.

Third, you can add an adjoint class for the constant types that require multi-step operations to complete the constructor .. The string class in the net framework follows this policy and uses the companion class system. Text. stringbuilter. You use the stringbuliter class to create a string in multiple steps. After all the required steps are completed to generate a string class, you get a constant string from stringbuilter.

Note :.. Net. Therefore, multiple operations on a string will produce a large amount of junk memory fragments. You can use stringbuliter to balance this problem .)

The constant type is simpler and easier to maintain. Do not blindly create get and set accessors for the attributes of each object. Your first choice for these types is to store these numbers as constant types and atomic types. From these entities, you can easily create more complex structures.

Related Article

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.