How to improve C # code quality

Source: Internet
Author: User

In this paper, "Effective C # Second Edition" is applied to the Unity game engine in the use of C # in the experience of the rule of thumb was refined, summed up into 21 (first summed up is 22, and later found that 22nd is also. NET features, unity version of Mono is not implemented, so strictly speaking 21) guidelines for you to quickly grasp the book's knowledge of the outline, in Unity to write more high-quality C # code.

"Effective C # Second Edition" originally had 50 principles, but these 50 principles are for the C # language itself as well. NET to write, I found in the reading process that there are some principles that do not apply to the use of mono versions of C # in unity. Therefore, in the summary of reading notes, the principle of non-applicability is omitted, at the same time the applicable principles are refined, summed up 21, the content of this article.

It is important to note that these serial numbers have been rearranged in order to facilitate reading, since the criteria used in the book have been singled out, leading to some jumps in the guideline numbers. After the rearrangement, the title in the book is not the same as the number of guidelines, at the end of the summary of the principle of the corresponding original book number.

Similarly, as a summary of the article, the content of each article is highly summarized, perhaps understand the steep slope, if you have read to not understand the place, suggest you go to read the original book, English and Chinese version can be, to see the original book provides a variety of code and examples, so that the mastery will be more effective.

Summary of the thinking Guide schema in this article

The following is a summary mind map of the 22 guidelines for improving the quality of C # code in unity in this article:

Principle 1 use attributes as much as possible instead of data members that are directly accessible

property has always been a relatively characteristic presence in the C # language. properties allow data members to be exposed as part of a common interface while still providing the required encapsulation in an object-oriented environment. The language element of the attribute allows you to use it as if it were a data member, but the underlying is still implemented using the method.

using attributes makes it very easy to include a check mechanism in the get and set code snippets.

It is important to note that because a property is implemented in a method, it has all the language features that the method has:

1) It is very convenient to add multi-threaded support to the properties. You can enhance the implementation of get and set accessors (accessors) to provide data access synchronization.

2) attributes can be defined as virtual.

3) You can extend the attribute to abstract.

4) You can use the generic version of the property type.

5) Attributes can also be defined as interfaces.

6) because the implementation of the access method get and set are independent of two methods, after C # 2.0, you can give them to define different access permissions, to better control the visibility of class members.

7) and in order to be consistent with multidimensional arrays, we can create multi-dimensional indexers that use the same or different types on different dimensions.

You should use attributes whenever you need to expose data in a public or protected interface of a type. You should also use an indexer to expose a sequence or dictionary if you can. Now devote a little more time to the use of properties, in exchange for future maintenance is more easy.

Principle 2 prefer to use run-time constants rather than compile -time constants

For constants, there are two different versions of C #: Run constant (readonly) and compile constant (const).

You should use the run-time constant instead of the compiler constants whenever possible. Although the compiler constants are slightly faster, they are not as flexible as running a constant amount. You should use compile-time constants only when those performance anomalies are sensitive, and the values of a constant never change between versions.

The difference between compile-time and run-times is that they are accessed differently because the ReadOnly value is parsed at runtime:

The value of the compile constant (const) is replaced directly by the value in the target code.

The value of the run-time constant (READONLY) is evaluated at runtime. The Il referenced by the runtime generates a reference to the readonly variable, not the value of the variable.

This difference brings up the following rules:

Compile-time constants (const) can only be used for numeric values and strings.

The run-time constant (readonly) can be any type. The run-time constant must be initialized in the constructor or initializer because it cannot be modified after the constructor is executed. You can let a readonly value be a datatime structure, and you cannot specify a const as Datatime.

You can save instance constants with the ReadOnly value to hold different values for each instance of the class. and the compile-time constant is static.

Sometimes you need to get a value to be determined at compile time, it is best to use the run constant (readonly).

The value of the tag version number should use a run-time constant, because its value will change with each release of the different versions.

Where Const is better than ReadOnly is just performance, using a known constant value is slightly higher than accessing the ReadOnly value, but the efficiency gains can be negligible.

In summary, be sure to use const when the compiler must have a numeric value. For example, the parameters of an attribute (attribute) and the definition of an enumeration, as well as those values that do not change between releases of each version. In all other cases, you should try to choose a more flexible readonly constant.

Principle 3 It is recommended to use the IS or as operator instead of forcing type conversions

In C #, the usage of the IS and as operators is summarized as follows:

Is : checks whether an object is compatible with other specified types and returns a bool value that never throws an exception.

As : The action is the same as coercion type conversion, but never throws an exception, that is, if the conversion is unsuccessful, NULL is returned.

Use the as operator as much as possible, because as is more secure and more efficient than forcing type conversions.

As is returned NULL when the conversion fails, and NULL when the converted object is null, so when converting with AS, you only need to check that the returned reference is null.

Neither the as nor the is operators perform any user-defined conversions, they are successful only if the runtime type conforms to the target type, and no new objects are created at the time of conversion.

The as operator is not valid for value types, which can be converted using is, with coercion type conversions.

The IS operator should only be used if you cannot convert using as. Otherwise, is superfluous.

principle 4 It is recommended to use the Conditional property instead of the # if conditional compilation

Because of the #if/#endif很容易被滥用, the code that is written is difficult to understand and more difficult to debug. C # provides a conditional attribute (Conditional attribute) for this purpose. You can use conditional attributes to split a function so that it compiles and becomes part of the class only after you have defined some environment variables or set a value. The most common place to conditional features is to turn a piece of code into a debug statement.

The conditional attribute can only be applied to the entire method, and any method that uses the conditional attribute can only return a void type. You can no longer apply the conditional attribute on a code block within a method. It is also not possible to apply the conditional attribute on a method that has a return value. However, a method that applies the conditional attribute can accept any number of reference type parameters.

The IL generated using the conditional attribute is #Eendif时更有效率 than using #if/. At the same time, restricting it at the function level allows for a clearer separation of conditional code to further guarantee the good structure of the code.

Principle 5 Understanding the relationship between several equivalence judgments

There are two types that can be created in C #: Value types and reference types. If a variable of two reference types points to the same object, they are considered "reference equality." If the variable types of the two value types are the same and contain the same content, they are considered "value equal." This is why the equivalence judgment requires so many methods.

When we create our own type, whether it is a class or a struct, you should define the meaning of "equivalence" for the type. C # provides 4 different functions to determine whether two objects are "equal".

1) public static bool ReferenceEquals (Object-left, object-right); Determines whether the object identity of two different variables is equal. Regardless of whether a reference type or a value type is being compared, the method is judged by the object identity, not the object content.

2) public static bool Equals (Object-left, object-right); Used to determine whether the run-time type of two variables is equal.

3) public virtual bool Equals (object right); For overloading

4) public static bool operator = = (MyClass left, MyClass right); For overloading

Object.referenceequals () Static methods and Object.Equals () static methods should not be covered because they have completed the work required to complete, provide the correct judgment, and the judgment is independent of the specific type of runtime. For value types, we should always overwrite Object.Equals () instance methods and operatior== () to provide them with more efficient equivalence judgments. For reference types, you need to override the Object.Equals () instance method only if you think that the meaning of equality is not equal to the object identity. The iequatable is also implemented when you overwrite Equals ().

PS: This principle corresponds to Principle 6 in "effectivec# Second Edition".

Principle 6 Learn about some of the pits in GetHashCode ()

The GetHashCode () method will have a lot of pits when used and should be used with caution. The GetHashCode () function is only used in one place, that is, when the hash value of a key is defined for a hash-based collection, such collections include HashSet and dictionary<k,v> containers, and so on. For reference types, it works fine, but is inefficient. For value types, implementations in the base class are sometimes even incorrect. Moreover, it is impossible to write your own GetHashCode () to be both efficient and correct.

In. NET, each object has a hash code, whose value is determined by System.Object.GetHashCode ().

When implementing your own GetHashCode (), follow these three principles:

1) If two objects are equal (defined by operation==), then they must generate the same hash code. Otherwise, such a hash code will not be used to find objects in the container.

2) A,a.gethashcode () must remain unchanged for any one object.

3) for all inputs, the hash function should randomly generate the hash code for each integer. This allows the hash container to get enough efficiency gains.

PS: This principle corresponds to principle 7 in "effectivec# Second Edition".

Principle 7 understand the advantages of a short approach

Translating C # code into an executable machine code requires two steps.

The C # compiler generates IL and places it in an assembly. The JIT then generates the machine code for the method (or a set of methods, if it involves inline), as needed. A short approach allows the JIT compiler to better split the cost of compiling. The short method is also more suitable for inline.

In addition to being short, simplifying the control process is also important. The fewer control branches, the more easily the JIT compiler will find the variables that best fit in the register.

Therefore, the advantage of the short method is not only reflected in the readability of the code, but also related to the efficiency of the program running.

PS: This principle corresponds to principle 11 in "Effectivec# Second Edition".

principle 8 Select variable initialization instead of assignment statement

Member initializers are the simplest way to guarantee that a member in a type is initialized-regardless of which constructor is called. The initializer executes before all constructors are executed. Using this syntax also guarantees that you will not add new constructors when you omit important initialization code.

In summary, if all constructors are to initialize a member variable to the same value, the initializer should be used.

PS: This principle corresponds to principle 12 in "Effective C # Second Edition".

Principle 9 initialize static member variables correctly

C # provides initialization that has static initializers and static constructors that are dedicated to static member variables.

A static constructor is a special function that will be executed before all other methods are executed and before the variable or property is accessed for the first time. You can use this function to initialize a static variable to implement a singleton pattern or any action that must be performed before the class is available.

As with instance initialization, you can also use initializer syntax to override static constructors. If you just need to allocate space for a static member, you might want to use the initializer syntax. If you want more complex logic to initialize static member variables, you can use a static constructor.

The most common reason to use static constructors instead of static initializers is to handle exceptions. When using static initializers, we cannot catch exceptions ourselves. It can be done in a static constructor.

PS: This principle corresponds to principle 13 in "Effective C # Second Edition".

principle Ten use a chain of constructors (reduce repetitive initialization logic)

Writing a constructor is often a repetitive labor, and if you find that multiple constructors contain the same logic, you can extract this logic into a common constructor. This avoids code duplication, or it can be used to generate more efficient target code using the constructor initializer.

The C # compiler will treat the constructor initializer as a special syntax, removing the duplicate variable initializers and repeating base class constructor calls. This allows the final object to execute a minimal amount of code to ensure the correctness of the initialization.

Constructor initializers allow one constructor to call another constructor. C # 4.0 adds support for default parameters, which can also be used to reduce duplicate code in constructors. You can unify all constructors of a class into one and specify default values for all optional parameters. Several other constructors call a constructor and provide different parameters.

PS: This principle corresponds to principle 14 in "effectivec# Second Edition".

principle implementation of standard destruction modes

The GC can efficiently manage the memory used by the application. However, it still takes time to create and destroy objects on the heap. If you create too many reference objects in a method, you will have a serious effect on the performance of the program.

Here are some rules that can help you minimize the amount of GC work:

1) If a local variable of a reference type (value type does not matter) is used in a frequently called routine, it should be promoted to a member variable.

2) provides static objects for commonly used type instances.

3) Create the final value of the immutable type. For example, the + = operation of the string class Inode creates a new string object and returns, and multiple uses produce a lot of garbage, not recommended. For simple string manipulation, string is the recommended use. Format. For complex string operations, it is recommended to use the StringBuilder class.

PS: This principle corresponds to principle 16 in "effectivec# Second Edition".

principle distinguishing between value types and reference types

In C #, class corresponds to the reference type, and the struct corresponds to the value type.

C # is not C + +, and you cannot define all types as value types and create references to them when needed. C # is not Java, and unlike Java, all things are reference types. It is important that you determine the behavior of the type when you create it, because later changes can cause a lot of catastrophic problems.

Value types cannot be polymorphic, so their best use is to store data. The reference type supports polymorphism, so it is used to define the behavior of the application.

In general, we are accustomed to use class, arbitrarily create the most is the reference type, if the following are certain, then you should create a struct value type:

1) is the primary responsibility for this type of data storage?

2) are the public interfaces of this type defined by the properties that access their data members?

3) Are you sure that the type will never have a derived type?

4) Are you sure that the type will never need polymorphic support?

Use a value type to represent the type of underlying stored data, and use reference types to encapsulate the behavior of the program. In this way, you can ensure that the data exposed by the class is provided safely in the form of replication, as well as memory performance improvements based on stack storage and using inline storage, and can be used to express application logic using standard object-oriented techniques. If you are unsure of the type of future diagram, you should choose a reference type.

PS: This principle corresponds to principle 18 in "Effective C # Second Edition".

principle guaranteed 0 is a valid state for a value type

When you create a custom enumeration value, make sure that 0 is a valid option. If you define flag, you can define 0 as a flag with no status selected (for example, none). That is, the enumeration value that is used as a token (that is, the flags attribute is added) should always be set to 0.

PS: This principle corresponds to principle 19 in "Effective C # Second Edition".

Principle 14

Guaranteed constants and atomicity of value types

The constant type makes our code easier to maintain. Do not blindly create get and set accessors for each property in the type. For those types of data that are intended to be stored, the constants and atomicity should be guaranteed as much as possible.

PS: This principle corresponds to principle 20 in "Effective C # Second Edition".

principle restricting the visibility of types

Under the premise that the type can do its job. You should assign the minimum visibility to the type as much as possible. That is, exposing only those that need to be exposed. Try to implement a public interface using classes with lower visibility. The lower the visibility, the less code that can access your functionality, and the fewer changes that may occur in the future.

PS: This principle corresponds to principle 21 in "Effective C # Second Edition".

principle override inheritance by defining and implementing an interface

understand the differences between abstract class and interface (interface):

1) interface is a kind of contract design, a type that implements an interface, must implement the method agreed in the interface. Abstract base classes provide a common abstraction for a set of related types. That is, the abstract base class describes what the object is, and the interface describes how the object will behave.

2) An interface cannot contain an implementation, nor can it contain any specific data members. Abstract base classes can provide some concrete implementations for derived classes.

3) The base class describes and implements a set of common behaviors among related types. The interface defines a set of atomic functions that can be implemented by other unrelated concrete types.

By understanding the difference between the two, we can create more expressive, more responsive designs. Use the class hierarchy to define the related types. Expose functionality with interfaces and allow different types to implement these interfaces.

PS: This principle corresponds to principle 22 in "effectivec# Second Edition".

principle understand the difference between an interface method and a virtual method

At first glance, there seems to be no difference between implementing an interface and covering a virtual method, in fact, there is a great difference between implementing an interface and covering a virtual method.

1) A member method declared in an interface is not a virtual method by default, so a derived class cannot overwrite a non-virtual interface member implemented in a base class. To overwrite, declare the interface method as virtual.

2) A base class can provide a default implementation for a method in an interface, and then a derived class can declare that it implements the interface and inherit the implementation from the base class.

3) Implementing an interface has more choices than creating and overwrite virtual methods. We can create a sealed (sealed) implementation, a virtual implementation, or an abstract contract for the class hierarchy. You can also create a sealed implementation and provide a virtual method to invoke in the method that implements the interface.

PS: This principle corresponds to principle 23 in "effectivec# Second Edition".

principle implementing callbacks with delegates

In C #, callbacks are implemented with delegates, and the main points are as follows:

1) The delegate provides us with a type-safe callback definition. Although most common delegate applications are related to events, this is not the case for a C # delegate application. A delegate is the best choice when there is a need for communication between classes, and we expect a more loosely coupled mechanism than the one provided by the interface.

2) The delegate allows us to configure the target at run time and notify multiple customer objects. The delegate object contains an application of a method that can be either a static method or an instance method. That is, with a delegate, we can communicate with one or more client objects that are linked at run time.

3) because callbacks and delegates are so common in C #, C # specifically provides them with a thin syntax in the form of lambda expressions.

4) Due to some historical reasons. The delegates in net are multicast delegates (multicast delegate). Each target is called sequentially during a multicast delegate invocation. The delegate object itself does not catch any exceptions. As a result, any exception thrown by the target will end the invocation of the delegate chain.

PS: This principle corresponds to principle 24 in "effectivec# Second Edition".

principle implementing notifications with event patterns

Events provide a standard mechanism for notifying listeners, and events in C # are actually a syntactically fast implementation of the Observer pattern.

An event is a built-in delegate that provides a type-safe method signature for an event handler function. Any number of customer objects can register their handlers with the event, and then handle these events, which do not have to be given by the compiler, and the event does not have to be a subscriber to work properly.

Using events in C # can reduce the coupling between the sender and the possible notification recipient, which can be developed entirely independently of the recipient.

PS: This principle corresponds to principle 25 in "effectivec# Second Edition".

principle avoid returning a reference to an inner class object

If the reference type is exposed to the outside world through the public interface, then the object's consumer can change the internal structure of the object by bypassing our defined methods and properties, which can lead to common errors.

There are four different strategies that prevent intentional or unintentional modification of data structures inside types:

1) value type. When customer code accesses a value type member through a property, the actual return is a copy of the object of the value type.

2) constant type. such as System.String.

3) define the interface. Restrict customer access to internal data members to a subset of features.

4) wrapper (wrapper). Provides a wrapper that exposes only the wrapper, thereby restricting access to objects in it.

PS: This principle corresponds to principle 26 in "Effective C # Second Edition".

principle handling base class updates with the new modifier only

Use the new operator to decorate a class member to redefine a non-virtual member that inherits from the base class.

The new modifier is only used to resolve a conflict between the base class method and the derived class method caused by upgrading the base class.

The new operator must be used with caution. If arbitrary abuse, it will cause the object invocation method of two semantics.

PS: This principle corresponds to principle 33 in "Effective C # Second Edition"

How to improve C # code quality

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.