Introduction:
In the previous topic, I introduced the causes for the introduction of generics in C #2.0 and the benefits of generics. However, the previous topic introduced some basic knowledge about generics, there is no reason why the performance of the generic type is higher than that of the non-generic type, so I will introduce the reasons and other knowledge about the generic type in this topic.
I. Generic Type and type parameters
Generic types are the same as other int and string types. generic types have two forms: generic types (including classes, interfaces, delegates, and structures, but there is no generic enumeration) or generic method. What kind of classes, interfaces, delegation, and methods are called generic types? In my understanding, type parameters in classes, interfaces, delegates, structures, or methods are generic types. In this way, the concept of type parameters exists. Type parameter -- is a placeholder of a real type (I think of a very vivid metaphor, for example, when you are at school, there are a lot of people in the canteen during the first to noon class, so many of them should use the habit of taking the place of books. Books are equivalent to a placeholder. They actually sit on their own positions, of course. When it comes to taking the place, I have heard my classmates say before, there was a very awesome MM in their class. I used my cell phone to take up my seat when I finished my class at noon. When I got it back, my cell phone disappeared. Then I told my classmates after hearing it, this girl in your class is awesome, and we will see it later). In the generic declaration, the type parameter must be placed in a pair of angle brackets (that is, the symbol <> ), multiple types of parameters are separated by commas. For example, T in the List <T> class is a type parameter. When using a generic type or method, we should replace it with a real type, just like using a book to take a seat, a book is only temporarily in that position. When you get a good meal, you have to switch to the position where you are sitting. The generic type in C # is also the same, the type parameter is currently in that position. In actual use, the actual type should be replaced by the actual type. In this case, we name the real type as the type real parameter, For example, in the Code of the previous topic, the real parameter of the type is int (instead of T ).
If the type parameter is not provided, an unbound generic type is declared. If the type parameter is specified, at this time, the type is called the constructed type (which can also be understood by the book position). However, the constructed type can be an open or closed type, here we first give the definition of these two concepts: Open Type -- type with type parameters is open type (all unbound generic types belong to open type ), closed Type -- the actual data type is passed for each type parameter. For Open types, we create open type instances.
Note:In the C # code, we can see that the only thing that is not bound to a generic type (except as a declaration) is in the typeof operator.
The following code is used to better illustrate this point:Copy codeThe Code is as follows: using System;
Using System. Collections. Generic;
Namespace CloseTypeAndOpenType
{
// Declare the open generic type
Public sealed class DictionaryStringKey <T>: Dictionary <string, T>
{
}
Public class Program
{
Static void Main (string [] args)
{
Object o = null;
// Dictionary <,> is an open type with two type parameters.
Type t = typeof (Dictionary <,> );
// Create an open instance (creation failed, exception occurred)
O = CreateInstance (t );
Console. WriteLine ();
// DictionaryStringKey <> is also an open type, but it has one type parameter.
T = typeof (DictionaryStringKey <> );
// Create an instance of this type (also failed, exception)
O = CreateInstance (t );
Console. WriteLine ();
// DictionaryStringKey <int> is a closed type.
T = typeof (DictionaryStringKey <int> );
// Create an instance of the closed type (successful)
O = CreateInstance (t );
Console. WriteLine ("object type =" + o. GetType ());
Console. Read ();
}
// Creation type
Private static object CreateInstance (Type t)
{
Object o = null;
Try
{
// Use the default constructor of the specified type t to create an instance of this type
O = Activator. CreateInstance (t );
Console. WriteLine ("instances with {0} created", t. ToString ());
}
Catch (Exception ex)
{
Console. WriteLine (ex. Message );
}
Return o;
}
}
}
The running result is (from the results, we can also see that the open type cannot create an instance of this type, and the exception information indicates that the type contains generic parameters ):
Ii. static fields and static constructors in generic types
First, the instance field belongs to an instance, and the static field belongs to the declared type. That is, if a static field is declared in a Myclass class, no matter how many instances of Myclass are created or how many instances are generated from Myclass, there is only one Myclass. x field. However, each closed type has its own static field (when a type parameter is used, CLR will actually define a new type object, so each static field is a static field in a different object, therefore, each has its own value.) use the following code to better illustrate-each closed type has its own static field:
Copy codeThe Code is as follows: View Code
Namespace GenericStaticFieldAndStaticFunction
{
// Generic class with a type parameter
Public static class TypeWithStaticField <T>
{
Public static string field;
Public static void OutField ()
{
Console. WriteLine (field + ":" + typeof (T). Name );
}
}
// Non-generic class
Public static class NoGenericTypeWithStaticField
{
Public static string field;
Public static void OutField ()
{
Console. WriteLine (field );
}
}
Class Program
{
Static void Main (string [] args)
{
// When a type argument is used, the CLR will actually define a new type object.
// Therefore, each static field is a different static field in the object, so that each has its own value.
// Assign values to the static fields of the generic type
TypeWithStaticField <int>. field = "1 ";
TypeWithStaticField <string>. field = "2 ";
TypeWithStaticField <Guid>. field = "3 ";
// At this time, the filed value only has one value, and each assignment changes the original value.
NoGenericTypeWithStaticField. field = "non-generic static field 1 ";
NoGenericTypeWithStaticField. field = "non-generic static field 2 ";
NoGenericTypeWithStaticField. field = "non-generic static field 3 ";
NoGenericTypeWithStaticField. OutField ();
// Verify that each closed type has a static field
TypeWithStaticField <int>. OutField ();
TypeWithStaticField <string>. OutField ();
TypeWithStaticField <Guid>. OutField ();
Console. Read ();
}
}
}
Running result:
Similarly, each closed type has a static constructor. The following code can help you better understand this:Copy codeThe Code is as follows: // an example of a static Constructor
Public static class Outer <Tx>
{
// Nested class
Public class Inner <Ty>
{
// Static Constructor
Static Inner ()
{
Console. WriteLine ("Outer <{0}>. Inner <{1}>", typeof (Tx), typeof (Ty ));
}
Public static void Print ()
{
}
}
}
Class Program
{
Static void Main (string [] args)
{
# Demonstration of region static functions
// The static constructor runs multiple times.
// Because each closed type has a separate static Constructor
Outer <int>. Inner <string>. Print ();
Outer <int>. Inner <int>. Print ();
Outer <string>. Inner <int>. Print ();
Outer <string>. Inner <string>. Print ();
Outer <object>. Inner <string>. Print ();
Outer <object>. Inner <object>. Print ();
Outer <string>. Inner <int>. Print ();
Console. Read ();
# Endregion
}
}
Running result:
From the running results, we may find that 7 of our code needs to be output, but only 6 results are output in the result, because any closed type Static constructor only executes once, outer <string>. inner <int>. print (); this line does not produce 7th rows of output, Because Outer <string>. inner <int>. the static constructor of Print (); has been executed before (the third row has been executed ).
Iii. How does the compiler parse generics?
In the previous topic, I posted the comparison results of generics and non-generics to show that generics have high performance advantages, but did not give the specific reasons that lead to higher efficiency of generics than non-generics, this section analyzes the specific causes of generic efficiency.
Here we will first post the code that describes the benefits of generic high performance in the previous topic, and then check the IL code to demonstrate the high performance of generic (for generic and non-generic types, C # How the compiler parses the code into IL ):Copy codeThe Code is as follows: using System;
Using System. Collections;
Using System. Collections. Generic;
Using System. Diagnostics;
Namespace GeneralDemo
{
Public class Program
{
Static void Main (string [] args)
{
Stopwatch stopwatch = new Stopwatch ();
// Non-Generic Array
ArrayList arraylist = new ArrayList ();
// Generic Array
List <int> genericlist = new List <int> ();
// Start timing
Stopwatch. Start ();
For (int I = 1; I <10000000; I ++)
{
// Genericlist. Add (I );
Arraylist. Add (I );
}
// End Time
Stopwatch. Stop ();
// Time used for output
TimeSpan ts = stopwatch. Elapsed;
String elapsedTime = String. Format }",
Ts. Hours, ts. Minutes, ts. Seconds,
Ts. Milliseconds/10 );
Console. WriteLine ("running time:" + elapsedTime );
Console. Read ();
}
}
}
When a non-generic ArrayList array is used, the IL code is as follows (only some of the main intermediate code is posted here. For details, you can download the sample source code and use the IL disassembly program to view it ):Copy codeThe Code is as follows: IL_001f: ldloc.1
IL_0020: ldloc.3
IL_0021: box [mscorlib] System. Int32
IL_0026: callvirt instance int32 [mscorlib] System. Collections. ArrayList: Add (object)
IL_002b: pop
IL_002c: nop
IL_002d: ldloc.3
IL_002e: ldc. i4.1
IL_002f: add
In the above IL code, the Code marked with red is mainly used to perform the packing operation (the packing process must be an event to be consumed, just like sending a package in my life, it must take some time to package. the packing operation is the same. However, for generic types, you can avoid the packing operation. The following will post the IL code for generic types) -- this operation is also the root cause that affects non-generic performance rather than generic type. However, why is the ArrayList type boxed before calling the Add method to Add elements to the array? The reason is actually mainly in the Add method. You can use the Reflector reflection tool to view the Add method definition of ArrayList. Below is a prototype of the Add method:
As can be seen from the above, Add (objec value) needs to receive parameters of the object type. However, what we need to pass in the Code is an int real parameter, in this case, the boxing operation is required (the value type int is converted to the object reference type, which is the packing operation). This explains why the Add method is called to perform the packing operation, at the same time, it also shows the benefits of generic high performance.
The following is the IL code using the generic List <T> (as shown in the figure, when the generic List is used, the packing operation is not performed, which reduces the time required for packing, in this way, the operation is faster and the performance is better .) :
Iv. Summary
Speaking of this topic, the content of this topic is over. This topic mainly introduces other generic content, since I put other generic content in the next topic, if I put all the content in this topic, it will appear quite a lot, this is not conducive to your understanding and reading, so I will continue to introduce other generic content in the next topic.
The following is the source code of all the demos used in the generic topic: GeneralDemo_jb51.rar