At the end of 2005, Microsoft officially released C # 2.0, and the new version adds a lot of new features compared to the C # 1.x, the most important of which is support for generics. With generics, we can define a type-safe data structure without using the actual data type. This can significantly improve performance and get higher-quality code. Generics are not something new, they are functionally similar to C + + templates, templates have existed for C + + many years ago, and have a large number of mature applications on C + +.
This article discusses general problems with generics, such as why generics are used, how generics are written, constraints on data types in generics, the use of static members in generics, the question of the method overload in generics, the generic method, and so on, which allows us to understand generics and master generic applications in general, and to write simpler, A versatile and efficient application system.
What is a generic type
When we write a program, we often encounter the function of two modules is very similar, just one is the processing of int data, the other is processing string data, or other custom data types, but we have no way, can only write multiple methods to process each data type, because the method parameter type is different. Is there a way to pass in a common data type in a method so that you can't merge the code? The emergence of generics is a special solution to this problem. After reading this article, you will have a deeper understanding of generics.
Why use generics
Generics are a new feature in the 2.0 version of the C # language and the Common language runtime (CLR). Generics introduce the concept of type parameters into the. NET Framework, and type parameters make it possible to design classes and methods that defer the designation of one or more types to the time when client code declares and instantiates the class or method. For example, by using generic type parameter T, you can write a single class that other client code can use without introducing the cost or risk of a run-time cast or boxing operation
To understand the problem, let's look at the following code, which omits some of the content, but the function is to implement a stack that can handle only the INT data type:
public class Stack
{
Private int[] items;
private int count;
public Stack (int size)
{
Items = new Int[size];
Count = 0;
}
public void Push (int x)
{
For code clarity and simple familiarity with generics so that the stack is not considered beyond the size of the array
Items[count++]=x;
}
public int Pop ()
{
For code clarity and simple familiarity with generics it is therefore not considered when the stack is out of bounds when the array is empty
return Items[--count];
}
}
Class Test
{
static void Main ()
{
Stack s = new stack (10);
S.push (111);
S.push (222);
Console.WriteLine (S.pop () +s.pop ())
}
}
The above code works fine, but what happens when we need a stack to hold the string type? Many people would have thought of copying the above code, and changing int to string. Of course, this does not have any problem in itself, but a good program is not to do so, because he thought that if you need a long, node type of stack to do? Do you want to copy it again? A good programmer would think of using a common data type object to implement this stack:
public class Stack
{
Private object[] items;
private int count;
public Stack (int size)
{
Items = new Object[size];
Count = 0;
}
public void Push (object x)
{
Items[count++]=x;
}
public Object Pop ()
{
return Items[--count];
}
}
Class Test
{
static void Main ()
{
Stack s = new stack (10);
S.push ("111");
S.push ("222");
Console.WriteLine (String) S.pop () + (String) S.pop ())
}
}
This stack is well written, he is very flexible, can receive any data type, can be said once and for all. But in a comprehensive manner, it is not without blemish, and it is mainly manifested in:
When a stack processes a value type, a boxing, folding operation occurs, which allocates and reclaims a large number of variables on the managed heap, and if the amount of data is large, the performance penalty is severe. When working with reference types, there is no boxing and folding operations, but the use of a data type cast operation increases the burden on the processor.
There are more serious problems with casts on data types (assuming that the stack is an instance of a stack):
Node1 n1 = new Node1 ();
Stack. Push (N1);
Node2 N2 = (Node2) stack. Pop ();
The above code is completely no problem at compile time, but because of the push of a Node1 type of data, but in the pop is required to convert to the Node2 type, this will occur when the program runtime type conversion exception, but escaped the compiler check.
For the problem of the object type stack, we introduced generics, and he can solve these problems gracefully. Generics use a passing data type T instead of object, specifying the type of T when the class is instantiated, and the runtime (runtime) is automatically compiled into local code, and the efficiency and code quality are greatly improved, and the data type is guaranteed to be secure.
Using generics
The following is a generic to rewrite the stack above, with a generic data type T as a placeholder, waiting to be instantiated with an actual type instead. Let's take a look at the power of generics:
public class Stack<t>
{
Private t[] items;
private int count;
public Stack (int size)
{
Items = new T[size];
Count = 0;
}
public void Push (T x)
{
Items[count++]=x;
}
Public T Pop ()
{
return Items[--count];
}
}
Class is not changed, just the introduction of the generic data type T can be applied to any data type and is type-safe. The calling method for this class:
Instantiation can only hold classes of type int
Class Test
{
static void Main ()
{
stack<int> s = new stack<int> (10);
S.push (111);
S.push (222);
Console.WriteLine (S.pop () +s.pop ())
}
}
Instantiation can only hold classes of type string
Class Test
{
static void Main ()
{
stack<string> s = new stack<string> (10);
S.push ("111");
S.push ("222");
Console.WriteLine (S.pop () +s.pop ())
}
}
There are distinct differences between this class and the class implemented by object:
1. He is type-safe. If you instantiate a stack of type int, you cannot handle data of type string, and so do other data types.
2. No packing and folding boxes are required. This class, when instantiated, generates local code according to the data type passed in, and the local code data type is determined, so no boxing and folding boxes are required.
3. No type conversion is required.
Generics overview
1. Use generic types to maximize code reuse, protect type security, and improve performance.
2. The most common use of generics is to create collection classes.
The 3..NET Framework class library contains several new generic collection classes in the System.Collections.Generic namespace. You should use these classes as much as possible in place of ordinary classes, such as ArrayList in the System.Collections namespace.
4. You can create your own generic interfaces, generic classes, generic methods, generic events, and generic delegates.
5. A generic class can be constrained to access a method of a particular data type.
6. Information about the types used in a generic data type can be obtained through reflection at run time.
The above example has a simple explanation of the characteristics of generics, which can be seen in the following theory and extended knowledge, for further understanding.
The theory of generic class instantiation
C # generic classes at compile time, Mister into intermediate code IL, generic type T is just a placeholder. When instantiating a class, replacing T with a user-specified data type and generating native code from the immediate compiler (JIT), the native code already uses the actual data type, equivalent to a class written in the actual type, so the local code of the different enclosing classes is not the same. According to this principle, we can think that:
The different enclosing classes of a generic class are separate data types.
Examples:stack<int> and stack<string> are two classes that do not have any relationship at all, and you can see him as Class A and Class B, an explanation that is useful for understanding the static members of a generic class.
Constraints on data types in generic classes
When a programmer writes a generic class, it always intentionally or unintentionally assumes the generic data type T, which means that the T is generally not adaptable to all types, but how does it restrict the type of data that the caller passes in? This requires constraining the incoming data type by specifying the ancestor of T, which is the inherited interface or class. Because of the single-root inheritance of C #, a constraint can have multiple interfaces, but only one class at most, and the class must precede the interface. This is the new keyword for c#2.0:
public class node<t, v> where T:stack, IComparable
where V:stack
{...}
The constraints of the above generic classes indicate that T must inherit from stack and IComparable, v must be a stack or inherit from a stack, otherwise it will fail to compile with the compiler's type check.
Generic type T is not specified, but because all classes in C # inherit from object, he can only invoke the method of the object class in the writing of class node, which makes the program difficult to write. For example, your class design only needs to support two data types, int and string, and you need to compare the size of a variable of type T in a class, but these are not possible because object is not a method of comparing size. To solve this problem, just icomparable constraints on T, and in class node you can execute the CompareTo method on the instance of T. This problem can be extended to other user-defined data types.
What if you need to re-instantiate T in class node? Because class node does not know what constructors the class T actually has. To solve this problem, you need to use the new constraint:
public class node<t, v> where T:stack, new ()
where v:icomparable
Note that the new constraint can only be parameterless, so it is also required that the corresponding class stack must have an parameterless constructor, or the compilation will fail.
There are two main classes of data types in C #: Reference types and value types. Reference types such as all classes, value types are generally the most basic type of language, such as int, long, struct, etc., in the constraints of generics, we can also restrict a wide range of type T must be a reference type or must be a value type, the corresponding keyword is class and struct:
public class node<t, v> where T:class
where v:struct
Generic methods
Generics can be used not only on the class, but also on the method of the class, he can automatically adapt to various parameters according to the type of the method parameter, this method is called the generic method. Look at the following class:
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, and this class Stack2 extends the functionality of the stack (which can also be written directly in the stack), and he can press multiple data into the stack at once. Where push is a generic method, the invocation 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 ();
}//To this point, the value of S is 64321
Static member variables in a generic type
In c#1.x, we know that static member variables of a class are shared among different class instances, and that he is accessed through the class name. Due to the introduction of generics in c#2.0, there are some changes in the mechanism of static member variables: Static member variables are shared among the same enclosing classes, and are not shared among different enclosing classes.
This is also very easy to understand, because different enclosing classes have the same class name, but because different data types are passed in separately, they are completely different classes, such as:
Stack<int> a = new stack<int> ();
Stack<int> B = new stack<int> ();
stack<long> C = new stack<long> ();
class instances A and B are the same type, they share static member variables, but class instance C is a completely different type than A and B, so static member variables cannot be shared with a and B.
Static constructors in generics
Static constructor rules: There can only be one, and cannot have parameters, he can only be. NET runtime is called automatically, and cannot be called manually.
The principle of static constructors in generics is the same as for non-generic classes, simply by understanding the different enclosing classes in generics as different classes. The following two scenarios can trigger a static constructor:
1. The specific enclosing class is instantiated for the first time.
2. Any static member variable in a particular enclosing class is called.
Method overloading in a generic class
The overloads of the method are heavily applied in the. Net framework, and he requires that the overloads have different signatures. In a generic class, because the generic type T is not deterministic when the class is written, there are some considerations when overloading, which we illustrate with the following examples:
public class Node<t, v>
{
Public T Add (t A, V B)//First add
{
return A;
}
Public T Add (V A, T b)//second add
{
return b;
}
public int Add (int a, int b)//Third add
{
return a + B;
}
}
The above class is obviously, if both T and V are passed into int, the three add methods will have the same signature, but the class can still be compiled, and whether this will cause the call confusion will be judged when the class instantiates and invokes the Add method. Take a look at the following calling code:
Node<int, int> node = new Node<int, int> ();
Object x = Node.add (2, 11);
This instantiation of node causes three add to have the same signature, but it can be invoked successfully because he first matches the third Add. However, if the third add is removed, the above calling code will not compile, prompting the method to produce confusion because the runtime cannot select between the first add and the second Add.
Node<string, int> node = new node<string, int> ();
Object x = Node.add (2, "11");
These two lines of calling code compile correctly, because the incoming string and int make the three add have different signatures and, of course, can find the only matching Add method.
As the above example shows, the generics of C # are when the method of an instance is called to check if the overload is confusing, rather than checking at compile time for the generic class itself. It also draws an important principle:
Generic methods are overwritten when they have the same signature as the generic method.
Method overrides for generic classes
The main problem with method rewriting (override) is the recognition rules for method signatures, which, like the method overloads, refer to the method overloads of the generic class.
Scope of use of generics
This article is mainly about generics in the class, in fact, generics can also be used in class methods, interfaces, structures (structs), delegates and so on above, the use of the same method is similar, no longer described.
Use of C # generics [go]