Li Jianzhong (Nanjing University of Posts and Telecommunications (Cornyfield@263.net)
Component Programming does not abandon traditional object-oriented programming. On the contrary, component programming is the deepening and development of object-oriented programming. As the soul of object-oriented language, class has been widely used in C # language. Many very "sharp" component features are even directly packaged by class. Deep understanding of classes is naturally an important part of our "sharp XP.
Class
C # is a data structure that encapsulates data members, function members, and nested types. The data member can be a constant or a field. Function members can be methods, attributes, indexers, events, operators, instance builders, static constructors, and destructor. We will make a detailed analysis of these members and their features in "lecture 5 constructor and destructor" and "lecture 6 domain method attributes and indexer. Except for some imported external methods, the declarations and implementations of classes and their members in C # are usually put together.
C # use a variety of modifiers to express the different properties of the class. Class C # of its protection level has five different restrictions and modifiers:
- Public can be accessed at will;
- Protected can only be accessed by this class and its inherited sub-classes;
- Internal can only be accessed by all classes in the Assembly. The combination is the logical unit and physical unit after the classes in C # language are combined, the compiled file extension is often ". DLL or. EXE ".
- Protected internal is a unique composite modifier that can only be accessed by all classes in the current composite and the inherited sub-classes of these classes.
- Private can only be accessed by this class.
If it is not a nested class, only the public and internal classes in the namespace or compiling unit are modified.
The new modifier can only be used for nested classes, indicating hiding the type that inherits the same name of the parent class.
Abstract is used to modify abstract classes, indicating that the class can only be used as the parent class for inheritance, but cannot be instantiated. Abstract classes can contain abstract members, but they are not required. Abstract cannot be used together with new. The following is the pseudo code used in the abstract class:
Abstract class A {public abstract void F ();} abstract class B: A {public void g () {}} Class C: B {public override void F () {// Implementation of method f }}
Abstract class A contains an abstract method F (), which cannot be instantiated. Class B inherits from Class A, which contains an instance method g (), but does not implement the abstract method F (), so it must still be declared as an abstract class. Class C inherits from Class B and implements class abstraction method F (). Therefore, objects can be instantiated.
Sealed is used to modify a class as a sealed class to prevent the class from being inherited. At the same time, abstract and sealed modifications to a class are meaningless and prohibited.
Object and this keyword
The distinction between classes and objects is crucial for us to grasp OO programming. We say that a class is an encapsulation of its members, but the encapsulation design of the class is only the first step in our programming, and objects of the class are instantiated, implementing operations on its data members is the foundation for us to complete practical tasks. The instantiated object adopts the myclass myobject = new myclass () syntax. The new semantics here calls the corresponding builder. C # all objects will be created on the managed stack. The instantiated type is called an object. Its core feature is that it has a copy of its own unique data member. The data members held by these special objects are called instance members. On the contrary, data members that are not held by special objects are called static members and declared with static modifiers in the class. Static function members that only perform operations on static data members. In C #, static data members and function members can only be obtained through class name reference. See the following code:
using System;class A{public int count;public void F(){Console.WriteLine(this.count);}public static string name;public static void G(){Console.WriteLine(name);}}class Test{public static void Main(){A a1=new A();A a2=new A();a1.F();a1.count=1;a2.F();a2.count=2;A.name="CCW";A.G();}}
We declare two A objects A1 and A2. For instance members count and F (), we can only reference them through A1 and A2. For static member names and g (), we can only reference them through type A, but not a1.name or a1.g ().
In the above program, we can see that we use this to reference the variable count in the instance method F. What does this mean? This keyword references the members of the current object instance. In the instance method body, we can also omit this and directly reference count. In fact, they have the same semantics. Of course, the static member function does not have the this pointer. This keyword is generally used to access members from constructors, instance methods, and instance accessors.
In the constructor, this is used to restrict hidden members with the same name. For example:
class Employee{public Employee(string name, string alias) { this.name = name; this.alias = alias;}}
This is also used to express an object as a parameter when it is passed to other methods. For example:
CalcTax(this);
This is even more indispensable when the indexer is declared, for example:
public int this [int param]{ get { return array[param]; } set { array[param] = value; }}
System. Object Class
All classes in C # are directly or indirectly inherited from the system. Object Class, which enables the class in C # to be inherited by a single root. If the inheritance class is not explicitly specified, the compiler determines that the class inherits from the system. Object Class by default. The system. object class can also be expressed by the lower-case object keyword. The two are exactly the same. Naturally, all classes in C # inherit the public interfaces of the system. object class, and it is very important for us to understand and master the behavior of classes in C. The following is a system. object class that is expressed in the form of an interface only:
namespace System{public class Object{public static bool Equals(object objA,object objB){}public static bool ReferenceEquals(object objA,object objB){}public Object(){}public virtual bool Equals(object obj){}public virtual int GetHashCode(){}public Type GetType(){}public virtual string ToString(){}protected virtual void Finalize(){}protected object MemberwiseClone(){}}
Let's first look at the two static methods of the object: equals (Object obja, object objb), referenceequals (Object obja, object objb), and an instance method equals (Object OBJ ). Before describing these two methods, we must first understand the two important equality concepts of object-oriented programming: equal values and equal references. Equal values mean that their data members are equal by memory bit. When the references are equal, they point to the same memory address, or their object handles are equal. If the reference value is equal, the reference value must be equal. For the value type relationship equal sign "=", determine whether the two values are equal (the structure type and enumeration type have no defined relationship equal sign "=", we must define it ourselves ). For the reference type relationship equal sign "=", determine whether the two references are equal. The Value Type in C # usually does not reference equal representation. It is used to indirectly judge whether the addresses of the two are equal only by using the "&" in the unmanaged programming.
The static method equals (Object obja, object objb) first checks whether both objects obja and objb are null. If yes, true is returned; otherwise, obja is performed. equals (objb) calls and returns its value. The problem comes down to the instance method equals (Object OBJ ). The default implementation of this method is {return this = OBJ;}, that is, to determine whether two objects are referenced to be equal. However, we noticed that this method is a virtual method. C # we recommend that you rewrite this method to determine whether the values of the two objects are equal. Microsoft. the method is overwritten by many types provided in the. NET Framework class library, such as: system. string (string), system. int32 (INT), but some types do not overwrite this method, such as: system. array, we must pay attention to it when using it. For the reference type, if the instance method equals (Object OBJ) is not overwritten, the call to it is equivalent to this = OBJ, that is, the reference equals judgment. All value types (implicitly inherited from the system. valuetype class) overwrite the instance method equals (Object OBJ) to determine whether the values are equal.
Note that false is returned for object X, X. Equals (null). Here, X cannot be null (otherwise, equals () cannot be called, and the system throws a null reference error ). Here we can also see the reason for designing the static method equals (Object obja, object objb)-If both objects obja and objb can be null, we can only use object. equals (Object obja, object objb) is used to determine whether the values are equal. Of course, if we do not rewrite the instance method equals (Object OBJ), we still get the result of referencing equal values. We can implement the interface icomparable (We will elaborate on the interface inheritance and Polymorphism in section 7) to force rewrite the instance method equals (Object OBJ ).
For the value type, the instance method equals (Object OBJ) should be consistent with the return value of the equal sign "=", that is, if we overwrite the instance method equals (Object OBJ ), we should also overload or define the equal sign "=" operator, and vice versa. Although the value type (inherited from system. valuetype class) All override the instance method equals (Object OBJ), but C # we recommend that you override your own value type instance method equals (Object OBJ), because the system. valuetype class rewriting is inefficient. For the reference type, we should override the instance method equals (Object OBJ) to express equal values. Generally, we should not overload the equal sign "=" operator because its default syntax is to judge that the reference is equal.
The static referenceequals (Object obja, object objb) method is used to determine whether two objects are referenced to be equal. If the two objects are of reference type, their semantics is the same as that of the equal sign "=" operator without overloading. If two objects are of the value type, the return value must be false.
The instance method gethashcode () provides the hash value for the corresponding type and applies it to the hash algorithm or hash table. It should be noted that if we overwrite the equals (Object OBJ) method of an instance type, we should also overwrite the instance method gethashcode () -- this is the case, the values of the two objects are equal, their hash codes should also be equal. The following code is a good example of the previous methods:
Using system; struct a {public int count;} Class B {public int number;} class c {public int integer = 0; Public override bool equals (Object OBJ) {c = OBJ as C; If (C! = NULL) return this. integer = C. integer; elsereturn false;} public override int gethashcode () {return 2 ^ integer;} class test {public static void main () {A A1, A2; a1.count = 10; a2 = A1; // console. write (a1 = a2); the "=" operator console is not defined. write (a1.equals (A2); // trueconsole. writeline (object. referenceequals (a1, a2); // falseb b1 = new B (); B b2 = new B (); b1.number = 10; b2.number = 10; console. write (b1 = b2); // falseconsole. write (b1.equals (B2); // falseconsole. writeline (object. referenceequals (b1, b2); // falseb2 = b1; console. write (b1 = b2); // trueconsole. write (b1.equals (B2); // trueconsole. writeline (object. referenceequals (b1, b2); // truec C1 = new C (); C C2 = new C (); c1.integer = 10; c2.integer = 10; console. write (C1 = c2); // falseconsole. write (c1.equals (C2); // trueconsole. writeline (object. referenceequals (C1, C2); // falsec2 = C1; console. write (C1 = c2); // trueconsole. write (c1.equals (C2); // trueconsole. writeline (object. referenceequals (C1, C2); // true }}
As we expected, compile and run the program and we will get the following output:
Truefalse
Falsefalsefalse
Truetruetrue
Falsetruefalse
Truetruetrue
The instance method GetType () and typeof have the same semantics. They all query the object metadata to determine the object runtime type, we will elaborate on the feature and ing in the tenth lecture.
The instance method tostring () returns the string expression of the object. If this method is not overwritten, the system generally returns the type name as a string.
The protected finalize () method has special semantics in C #, which will be described in detail in "lecture 5 constructor and destructor.
The protected memberwiseclone () method returns a "Shadow Copy" of the current object. This method cannot be overwritten by the quilt class. "Shadow Copy" is only a bitwise copy of an object. It means to assign a value to the value type variable in the object and copy the reference type variable in it, that is, the copied reference variable will hold a reference to the same memory. Compared with "Shadow Copy", it performs value replication instead of handle replication on referenced variables. For example, X is an object that contains objects a and B, and object A contains references of object m. Y is a "Shadow Copy" of X ". Then y will have the same reference of A and B. However, for a "Deep copy" Z of X, it will have references to objects C and D, and an indirect reference to object n, where C is a copy of, D is a copy of B, and N is a copy of M. Deep copy is completed in C # by implementing the icloneable interface (providing the clone () method.
Object and system. the grasp of objects makes a good foundation for class learning, but this is only a small step in our sharp journey. It is related to object member initialization, memory reference release, inheritance and polymorphism, exception Handling and many other "sharp" special effects can be achieved, so we will continue to look forward to the following topics!