C # generic explanation

Source: Internet
Author: User

When writing a program, we often encounter two modules with similar functions. One is to process int data, the other is to process string data, or other custom data types, however, there is no way to write multiple methods to process each data type, because the parameter types of methods are different. Is there a way to pass in a common data type in the method so that the code can be merged? The emergence of generics specifically solves this problem. After reading this article, you will have a better understanding of generics.

Why use generic
To understand this problem, let's first look at the following Code. The Code omitted some content, but the function is to implement a stack, which can only process int data types:
Public class Stack
{
Private int [] m_item;
Public int Pop (){...}
Public void Push (int item ){...}
Public Stack (int I)
{
This. m_item = new int [I];
}
}
The code above runs well. But what should we do when we need a stack to save the string type? Many people will think of copying the code above and changing int to string. Of course, there is no problem in doing this, but a good program won't do this, because he thinks what to do if a stack of the long or Node type is needed in the future? Do I have to copy it again? A good programmer will think of using a common data type object to implement this stack:
Public class Stack
{
Private object [] m_item;
Public object Pop (){...}
Public void Push (object item ){...}
Public Stack (int I)
{
This. m_item = new [I];
}

}
This stack is well written. It is very flexible and can receive any data type. It can be said that it is once and for all. However, in a comprehensive sense, it is not defect-free, mainly manifested in:
When the Stack processing value type, there will be packing and folding operations, which will allocate and recycle a large number of variables on the managed Stack. If the data volume is large, the performance loss will be very serious.
When processing the reference type, although there is no packing or folding operation, the forced conversion operation of the data type will be used to increase the burden on the processor.
There are more serious problems in the forced conversion of data types (assuming that the stack is an instance of the Stack ):
Node1 x = new Node1 ();
Stack. Push (x );
Node2 y = (Node2) stack. Pop ();
The above code is completely correct during compilation, but because a Node1 data type is pushed, it is required to be converted to Node2 during Pop, this causes an exception in type conversion during the program running, but it escaped the compiler's check.

For the issue of object type stacks, we introduce generics to solve these problems elegantly. The generic type uses a data type T to replace the object. During class instantiation, the T type is specified, and the Runtime is automatically compiled as local code, the operation efficiency and code quality are greatly improved, and data type security is ensured.

Use generic
Below is a general data type T used as a placeholder to overwrite the above stack, waiting for an actual type to be replaced during instantiation. Let's take a look at the power of generics:
Public class Stack <T>
{
Private T [] m_item;
Public T Pop (){...}
Public void Push (T item ){...}
Public Stack (int I)
{
This. m_item = new T [I];
}
}
The Writing Method of the class remains unchanged, but the general data type T can be applied to any data type, and the type is secure. Call method of this class:
// Instantiate a class that can only store the int type
Stack <int> a = new Stack <int> (100 );
A. Push (10 );
A. Push ("8888"); // This row is not compiled because Class a only receives int-type data.
Int x = a. Pop ();

// Instantiation can only save classes of the string type
Stack <string> B = new Stack <string> (100 );
B. Push (10); // This row is not compiled because Class B only receives data of the string type.
B. Push ("8888 ");
String y = B. Pop ();

This class differs from the class implemented by the object:
1. It is type-safe. If a stack of the int type is instantiated, data of the string type cannot be processed. The same applies to other data types.
2. No packing or folding is required. This class generates local code according to the input data type during instantiation. The data type of the local code has been determined, so there is no need to pack or fold the box.
3. No type conversion is required.

Theory of generic class instantiation
C # When compiling a generic class, it becomes the intermediate code IL. generic type T is just a placeholder. During class instantiation, T is replaced by the data type specified by the user and local code is generated by the real-time Compiler (JIT). The actual data type is already used in the local code, it is equivalent to a class written by actual type, so the local code of different closed classes is different. Based on this principle, we can think like this:
Different closed classes of generic classes have different data types.
For example, Stack <int> and Stack <string> are two classes that have no relationship with each other. You can think of them as Class A and Class B, this explanation is helpful for understanding the static members of generic classes.

Constraints on Data Types in generic classes
When writing generic classes, programmers always intentionally or unintentionally make assumptions about the general data type T. That is to say, this T cannot adapt to all types in general, but how can we limit the Data Type passed in by callers? This requires constraints on the input data types by specifying the T ancestor, that is, the inherited interface or class. Because C # has a single inheritance, the constraint can have multiple interfaces, but only one class is allowed at most, and the class must be before the interface. Then the New Keyword C #2.0 is used:
Public class Node <T, V> where T: Stack, IComparable
Where V: Stack
{...}
The constraints of the above generic classes indicate that T must be inherited from the Stack and IComparable, and V must be inherited from the Stack or from the Stack. Otherwise, the compiler cannot check the type and compilation fails.
Generic Type T is not specified in particular, but because all classes in C # are inherited from objects, he can only call methods of the object class in writing class Node, this makes programming difficult. For example, your class design only needs to support two data types: int and string, and you need to compare the size of T-type variables in the class, but these cannot be implemented, because the object does not have a compare size method. To solve this problem, you only need to perform the IComparable constraint on T, then you can execute the CompareTo method on the T instance in the class Node. This problem can be extended to other user-defined data types.
What if T needs to be re-instantiated in class Node? Because the class Node does not know which constructors class T has. To solve this problem, we need to use the new constraint:
Public class Node <T, V> where T: Stack, new ()
Where V: IComparable
Note that the new constraint only has no parameters. Therefore, the corresponding class Stack must have a non-argument constructor. Otherwise, the compilation fails.
In C #, there are two types of data: reference type and value type. Reference types such as all classes. value types are generally the most basic types of languages, such as int, long, and struct. In the constraints of generics, we can also limit the type T to be a reference type or a value type in a wide range. The corresponding keywords are class and struct:
Public class Node <T, V> where T: class
Where V: struct

Generic Method
Generics can be used not only on classes, but also on class methods. They can automatically adapt to various parameters based on the type of method parameters. Such a method is called a generic method. Take a look at the following classes:
Public class Stack2
{
Public void Push <T> (Stack <T> s, params T [] p)
{
Foreach (T t in p)
{
S. Push (t );
}
}
}
The original class Stack can only Push one data at a time. This class Stack2 extends the Stack function (of course, you can also directly write it in the Stack). It can Push multiple data into the Stack at a time. Push is a generic method. The call example of this method is as follows:
Stack <int> x = new Stack <int> (100 );
Stack2 x2 = new Stack2 ();
X2.Push (x, 1, 2, 3, 4, 6 );
String s = "";
For (int I = 0; I <5; I ++)
{
S + = x. Pop (). ToString ();
} // At this point, the value of s is 64321


Static member variables in generics
In C #1. x, we know that static member variables of a class are shared among different class instances and accessed by class names. In C #2.0, the introduction of generics leads to some changes in the mechanism of static member variables: static member variables are shared among the same closed classes, and different closed classes are not shared.
This is also easy to understand, because different closed classes have the same class name, but they are completely different classes because different data types are passed in, such:
Stack <int> a = new Stack <int> ();
Stack <int> B = new Stack <int> ();
Stack <long> c = new Stack <long> ();
Class a and B are of the same type. They share static member variables, but class c is of the same type as a and B, therefore, static member variables cannot be shared with a and B.
Static constructor in generics
Static constructor rules: a static constructor can have only one and no parameters. It can only be called automatically by. NET runtime, but not manually.
The principle of static constructor in generics is the same as that of non-generic classes. You only need to understand different closed classes in generics as different classes. Static constructor can be stimulated in the following two cases:
1. The specified closed class is instantiated for the first time.
2. Any static member variable in a specific closed class is called.

Method overloading in generic classes
Method Overloading is widely used in. Net Framework, and it requires that the overloading have different signatures. In a generic class, because the generic type T is not determined during class writing, there are some precautions for the overload. The following examples illustrate these considerations:
Public class Node <T, V>
{
Public T add (T a, V B) // The first add
{
Return;
}
Public T add (V a, T B) // The second add
{
Return B;
}
Public int add (int a, int B) // The third add
{
Return a + B;
}
}
The above class is obvious. If both T and V are passed in int, the three add methods will have the same signature, but this class can still be compiled, whether it will cause call obfuscation will be determined when this class is instantiated and the add method is called. See the following call code:
Node <int, int> node = new Node <int, int> ();
Object x = node. add (2, 11 );
The Node instantiation causes three add to have the same signature, but the call is successful because it matches the third add first. However, if the third add is deleted, the code called above cannot be compiled and passed, prompting for obfuscation of the method, because it cannot be selected between the first add and the second add at runtime.
Node <string, int> node = new Node <string, int> ();
Object x = node. add (2, "11 ");
These two lines of calling code can be correctly compiled, because the input string and int make the three add with different signatures, of course, the unique matching add method can be found.
The above example shows that the C # Generic Type checks whether the overload is obfuscated when the instance method is called, rather than checking when the generic class is compiled. At the same time, an important principle is also drawn:
If the general method and generic method have the same signature, the generic method will be overwritten.

Method Rewriting for generic classes
The main problem with override is the method signature recognition rules. In this regard, it is the same as method overloading. For details, refer to method overloading of generic classes.

Generic usage scope
This article mainly describes generics in classes. In fact, generics can also be used in class methods, interfaces, structures (struct), and delegation. The usage is roughly the same, I will not talk about it any more.

 

Summary
C # generics are invaluable in the development tool library. They can improve performance, type security, and quality, reduce repetitive programming tasks, and simplify the overall programming model, all done through elegant, readable syntax. Although C # generics are based on the C ++ template, C # improves generics to a new level by providing compilation security and support. C # utilizes two-phase compilation, metadata, and innovative concepts such as constraints and general methods. There is no doubt that the future version of C # will continue to develop generics to add new features and extend generics to other. NET Framework fields such as data access or localization.

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.