When writing a program, we often encounter that the functions of the two modules are very similar, but 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 do we need to use generics? 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. This stack 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 very 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? Good programmers 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] ;}} the stack is well written. It is flexible and can receive any data type. But it is not completely defect-free, mainly because: When the stack processes the value type, there will be packing and folding operations, this will allocate and recycle a large number of variables on the hosting 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. Below the generic type is to use the generic type to override the above stack, and use a general data type T as a placeholder, waiting for the 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];} class is not changed, but the general data type T can be applied to any data type, and the type is safe. The call method of this class: // instantiation can only save the int type of class Stack <int> A = new stack <int> (100);. push (10);. push ("8888"); // This line of compilation fails, because Class A only receives int type data int x =. pop (); // instantiation only stores class stacks of the string type <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. The 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. According to this principle, we can think that different closed classes of generic classes are 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. When writing generic classes, programmers always intentionally or unintentionally make assumptions about the general data type T. That is to say, this t generally cannot adapt to all types, 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. In this case, 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, the new constraint only has no parameters. Therefore, the corresponding class Stack must have a no-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 restrict the type T to a wide range to be a reference type or a value type. 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 in classes, but also in class methods. They can automatically adapt to various parameters according to the type of method parameters, this method is called a generic method. See the following class: public class stack2 {public void push <t> (stack <t> S, Params T [] P) {foreach (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