Document directory
- 1.1.1 why is there a wildcard?
- 1.1.2 type parameter Constraints
- 1.1.3 generic Method
- 1.1.4 Summary
1.1 generic type in C #
One of the most criticized shortcomings of. NET 1.1 is that it does not provide support for generics. By using generics, We can greatly improve code reuse and obtain strong type support, avoiding implicit packing and unpacking, this improves the performance of applications to a certain extent. This article will systematically discuss generics for you. Let's start with understanding generics.
1.1 understand why generic 1.1.1 is available?
No matter how you enter the computer programming industryData structures and algorithmsThis topic. Because it is a basic discipline of computer science, the more underlying it is, the higher the requirements for time efficiency and space efficiency of data structures or algorithms. For example, when you call the sort () method to sort a set type (such as arraylist) instance, the. NET Framework is applied at the underlying layer.Quick sortingAlgorithm .. In the netframework, the fast sorting rule is named quicksort((, which is located in arraytheon. This can be viewed through the reflector.exe tool.
We don't want to discuss whether the quicksort () implementation is good, the efficiency is high or not high, which deviates from our topic. However, I would like to ask you to think about one question: If you implement a sorting algorithm, what would you do? Well, we can narrow down the questions. We will implement the simplest bubble sort algorithm. If you have no experience using generics, I guess you may not hesitate to write the following code, because this is the standard implementation of university courses:
Public class sorthelper {
Public void bubblesort (INT [] array ){
Int length = array. length;
For (INT I = 0; I <= length-2; I ++ ){
For (Int J = length-1; j> = 1; j --){
// Exchange two elements
If (array [J] <array [J-1]) {
Int temp = array [J];
Array [J] = array [J-1];
Array [J-1] = temp;
}
}
}
}
}
Readers who are not familiar with Bubble Sorting can safely ignore the method bodies of the above Code, which will not cause any obstacles to your understanding of generics, you only need to know the functions it implements: sorts the elements of an array in ascending order. We conduct a small test on this program:
Class program {
Static void main (string [] ARGs ){
Sorthelper sorter = new sorthelper ();
Int [] array = {8, 1, 4, 7, 3 };
Sorter. bubblesort (array );
Foreach (int I in array ){
Console. Write ("{0}", I );
}
Console. writeline ();
Console. readkey ();
}
}
Output:
1 3 4 7 8
We found it working well and were delighted to think that this was the best solution. Not long ago, we had to sort an array of the byte type, and our above sorting algorithm could only accept an array of the int type, although we knew they were completely compatible, because the byte type is a subset of the int type, but C # is a strong language, we cannot input a byte array in a place that accepts the int array type. Okay, it doesn't matter. Now the only way is to copy the code and change the signature of the method:
Public class sorthelper {
Public void bubblesort (INT [] array ){
Int length = array. length;
For (INT I = 0; I <= length-2; I ++ ){
For (Int J = length-1; j> = 1; j --){
// Exchange two elements
If (array [J] <array [J-1]) {
Int temp = array [J];
Array [J] = array [J-1];
Array [J-1] = temp;
}
}
}
}
Public void bubblesort (byte [] array ){
Int length = array. length;
For (INT I = 0; I <= length-2; I ++ ){
For (Int J = length-1; j> = 1; j --){
// Exchange two elements
If (array [J] <array [J-1]) {
Int temp = array [J];
Array [J] = array [J-1];
Array [J-1] = temp;
}
}
}
}
}
Okay, we have solved the problem again. Although I always think it is awkward, this code is ready to work and should not be abstracted or changed too early based on Agile Software development ideas, when a change occurs for the first time, use the fastest way to solve it. When the change appears for the second time, a better architecture and design will be conducted. This is intended to avoid excessive design, because it is likely that the second change will never happen, however, you have spent a lot of time and energy creating a "perfect design" that will never be used ". This is like a proverb, "Fool me once, shame on you. fool me twice, shame on me. ", it means" fooling me once, it's you bad; fooling me twice, it's me stupid ".
Good things are always difficult to last for a long time. We need to sort a char array quickly. Of course we can follow the byte array method and continue to adopt the copy and paste method, then modify the signature of the method. But unfortunately, we don't want to let it fool us twice, because no one wants to prove ourselves stupid, so it's time to think about a better solution.
By carefully comparing the two methods, we will find that the implementation of the two methods is exactly the same, except for the method signature, there is no difference. If you have developed a web site program, you will know that for some sites with a very large page views, in order to avoid server overload, static page generation is usually used, because URL rewriting still consumes a lot of server resources, but after being generated as an HTML static webpage, the server only returns the file requested by the client, which can greatly reduce the burden on the server.
When a static page is generated on the web, a common method is template generation. The specific method is to load the template each time a static page is generated, the template contains some placeholders marked with special characters. Then we read data from the database and replace the placeholders in the template with the read data, finally, save the template as a static html file on the server according to certain naming rules.
We found that the situation here is similar. Let's make an analogy: we regard the above method as a template, and regard its method signature as a placeholder, because it is a placeholder, it can represent any type, which is the same as the placeholder of the template when the static page is generated. It can be used to represent any data from the database. The placeholder is defined. Let's take a look at the signatures of these three methods:
Public void bubblesort (INT [] array)
Public void bubblesort (byte [] array)
Public void bubblesort (char [] array)
We can find that the best way to define placeholders is to replace int [], byte [], and char [] with placeholders. We can use t [] to represent this placeholder, t can represent any type, thus shielding the differences between the signatures of the three methods:
Public void bubblesort (T [] array ){
Int length = array. length;
For (INT I = 0; I <= length-2; I ++ ){
For (Int J = length-1; j> = 1; j --){
// Exchange two elements
If (array [J] <array [J-1]) {
T temp = array [J];
Array [J] = array [J-1];
Array [J-1] = temp;
}
}
}
}
Now it seems much refreshed, but we have discovered another problem: when we define a class that needs to reference other types than it, we can define a constructor with parameters, and then pass the required parameters from the constructor. But above, our parameter t itself is a type (similar to int, byte, Char, rather than a type instance, such as 1 and 'A '). Obviously, we cannot pass this T-type array in the constructor, because the parameters are all in the position of the type instance, and T is the type itself, and its position is incorrect. For example, the following is a common constructor:
Public sorthelper (type Instance name );
The expected constructor is:
Public sorthelper (type );
In this case, we need to use a special syntax to pass the T placeholder. Instead, we should define such a syntax to pass it:
Public class sorthelper <t> {
Public void bubblesort (T [] array ){
// Method implementation body
}
}
We add an angle bracket behind the class name to use this angle bracket to pass our placeholder, that is, the type parameter. Next, let's take a look at how to use it. When we need to sort an array of the int type:
Sorthelper <int> sorter = new sorthelper <int> ();
Int [] array = {8, 1, 4, 7, 3 };
Sorter. bubblesort (array );
When we need to sort a byte array:
Sorthelper <byte> sorter = new sorthelper <byte> ();
Byte [] array = {8, 1, 4, 7, 3 };
Sorter. bubblesort (array );
I believe you have discovered that everything you have done has actually implemented a generic class. This is a typical application of generics. We can see that by using generics, we greatly reduce repeated code and make our programs more fresh, A generic class is similar to a template. You can specify any type for this template as needed.
We are more professional now, and give a formal name for the placeholder of this section. In. net, it is calledType parameter)In the following section, we will learn the type parameter constraints.
1.1.2 type parameter Constraints
In fact, if you run the code above, you will find that it cannot even be compiled. Why? Consider the following question: if we define a type, it defines a book named book, which contains two fields: an int type ID, which is the book identifier; one is a string-type title, which indicates the title of the book. This is an example. To illustrate the problem without deviating from the topic, this book type only contains these two fields:
Public class book {
Private int ID;
Private String title;
Public book (){}
Public book (int id, String title ){
This. ID = ID;
This. Title = title;
}
Public int ID {
Get {return ID ;}
Set {id = value ;}
}
Public String title {
Get {return title ;}
Set {Title = value ;}
}
}
Now, we create an array of the book type and try to sort it by using the generic class defined in the previous section. I think the code should be like this:
Book [] bookarray = new book [2];
Book book1 = New Book (124, ". Net beauty ");
Book book2 = New Book (45, "C #3.0 secrets ");
Bookarray [0] = book1;
Bookarray [1] = book2;
Sorthelper <book> sorter = new sorthelper <book> ();
Sorter. bubblesort (bookarray );
Foreach (book B in bookarray ){
Console. writeline ("ID: {0}", B. ID );
Console. writeline ("title: {0} \ n", B. Title );
}
Maybe you still haven't seen any problems yet. If you think the code in the previous section is very common, let's take a closer look and take a look at the bubblesort () of the sorthelper class () to avoid looking back at the code in the previous section, I copied it:
Public void bubblesort (T [] array ){
Int length = array. length;
For (INT I = 0; I <= length-2; I ++ ){
For (Int J = length-1; j> = 1; j --){
// Exchange two elements
If (array [J] <array [J-1]) {
T temp = array [J];
Array [J] = array [J-1];
Array [J-1] = temp;
}
}
}
}
Although we are reluctant, the problem still arises. Since it is sorting, we will inevitably compare the size. We can see that the size of the two elements is compared during the exchange, so now, who is bigger than book1 and book2? John May say that book1 is large, because its ID is 124, while book2's ID is 45. John May say book2 is large, because its title starts with "C, the title of book1 is ". ". ). However, the program cannot be judged, and it does not know whether to compare according to John's standards or by Wang's standards. At this time, we need to define a rule for comparison.
In. net, the basic method to achieve the comparison is to implement the icomparable interface, which has two versions: generic version and non-generic version, because we are currently explaining generic, however, you may not understand generics. To avoid "deadlocks" in your thinking, we use non-generic versions. It is defined as follows:
Public interface icomparable {
Int compareto (Object OBJ );
}
If our book type has implemented this interface, it will be called as follows:
Book1.compareto (book2 );
If book1 is smaller than book2, an integer smaller than 0 is returned. If book1 is equal to book2, 0 is returned. If book1 is greater than book2, an integer greater than 0 is returned.
Next, let's use our book class to implement the icomparable interface. At this time, we are faced with the sorting standard problem. To put it bluntly, we should use Xiao Zhang's standard or Xiao Wang's standard, here let's use the small Zhang standard, sort the book according to the ID, modify the book class, and implement the icomparable interface:
Public class Book: icomparable {
// Code: The above implementation is omitted
Public int compareto (Object OBJ ){
Book book2 = (book) OBJ;
Return this. Id. compareto (book2.id );
}
}
To save space, I omitted the implementation above the book class. Note that the current book instance id and the passed book instance id are not compared in the compareto () method, instead, we delegate the comparison to the int type, because the int type also implements the icomparable interface. By the way, have you found a problem with the above Code? This compareto () method is a "common" method. To ensure that all types can use this interface, its parameters accept an object-type parameter. Therefore, to obtain the book type, we need to perform a downward forced conversion in the method. If you are familiar with object-oriented programming, you should think of a violation of the liskov replacement principle here. I cannot elaborate on this principle here, but I can only mention it: this principle requires that the method should not forcibly convert the parameters accepted by the method. Why? The purpose of defining the inheritance system is to make the base class implement the common responsibilities for the code and the subclass implement its own responsibilities. When you define a method to accept the base class, the design itself is excellent, but when you perform forced conversion inside the method, this inheritance system is broken, because although the method signature is interface-oriented, the method is still oriented to implementation programming.
Note:What is "downcast )"? Because the object is a base class of all types, the book class inherits from the object class. In this pyramid-like inheritance system, the object is located at the upper layer, and the book is located at the lower layer, so it is called "downward forced conversion ".
Now, let's get back to the topic. Since we have now enabled the book class to implement the icomparable interface, should our generic classes work? No, because we should remember that a generic class is a template class, which knows nothing about the type parameters passed during execution, and will not make any guesses, we know that the book class now implements icomparable, which is quite easy, but our sorthelper <t> generic class does not know. What should we do? We need to tell the sorthelper <t> class (tell the compiler accurately) that the T-type parameters it accepts must be comparable. In other words, the icomparable interface is implemented, this is the topic of this section: Generic constraints.
To ensure that the type parameter T must implement the icomparable interface, we can redefine sorthelper as follows:
Public class sorthelper <t> where T: icomparable {
// Code: Implementation omitted
}
The preceding Definition specifies that the type parameter T must implement the icomaprable interface. Otherwise, compilation fails to ensure that the method body can run correctly. Because t has now implemented icomparable, and the Members in the array are t instances, when you click the decimal point "after array [I]. ", the vs200 smart prompt will provide the icomparable Member, that is, the compareto () method. We modify the bubblesort () class so that it can use the compareto () method for comparison:
Public class sorthelper <t> where T: icomparable
{
Public void bubblesort (T [] array ){
Int length = array. length;
For (INT I = 0; I <= length-2; I ++ ){
For (Int J = length-1; j> = 1; j --){
// Exchange two elements
If (array [J]. compareto (array [J-1]) <0 ){
T temp = array [J];
Array [J] = array [J-1];
Array [J-1] = temp;
}
}
}
}
}
At this point, we run the Code defined above again and we will see the following output:
ID: 45
Title: Beauty of. net
ID: 124
Title: C #3.0 secrets
In addition to limiting the type parameter t to implement an interface, it can also constrain T as a structure, t as a class, t as a constructor, and T as inherited from a base class, however, I think it is a waste of time to list each of these usages. So I will not continue to discuss them here. Their concepts are completely the same, but there are some differences in the declared syntax. However, we believe that you can easily solve these differences by viewing msdn.
1.1.3 generic Method
Let's take another look at this question: if we have a very complex class that executes a variety of scientific operations based on a certain field, we call this class supercalculator. Its definition is as follows:
Public class supercalculator {
Public int superadd (int x, int y ){
Return 0;
}
Public int superminus (int x, int y ){
Return 0;
}
Public String supersearch (string key ){
Return NULL;
}
Public void supersort (INT [] array ){
}
}
This class has very high requirements on algorithms ,. the built-in fast Sorting Algorithm in the. NET Framework cannot meet the requirements. Therefore, we consider implementing our own sorting algorithm. Note that the parameter types accepted by the supersearch () and supersort () methods are different, so we 'd better define a generic model to solve this problem. We call this algorithm speedsort (). Since this algorithm is so efficient, we should define it as public, so that other types can be used, the Code may be similar to the following according to the knowledge learned in the previous two sections:
Public class supercalculator <t> where T: icomparable {
// Code: omitted
Public void speedsort (T [] array ){
// Code: Implementation omitted
}
}
Here we will introduce a question about the type design: specifically, it is not appropriate to put the speedsort () method in supercaculator. Why? Because their responsibilities are mixed up, supercaculator means "Super calculator", so all the public methods it contains should be related to computing, and speedsort () appears here not to mention, when we find that the name of a method has little to do with the name of the class, we should consider abstracting this method and placing it in a new class, even if this class only has one method.
This is just a demonstration. We know that this problem exists. Well, let's get back to the question. Although the supercalculator class can do what we need now, its usage becomes complicated. Why? Because the speedsort () method contaminated it, we had to add the type parameter T to the supercalculator class just to be able to use the speedsort () method, even if we didn't call speedsort () when creating a supercalculator instance, you must also accept a type parameter.
To solve this problem, we naturally think: is there a way to add the type parameter T to the method, rather than the entire class, that is, to reduce the scope of T's function. The answer is yes. This is the topic of this section: Generic method. Similarly, we only need to modify the signature of the speedsort () method to accept a type parameter. At this time, the supercalculator is defined as follows:
Public class supercalculator {
// Code: Other implementations
Public void speedsort <t> (T [] array) where T: icomparable {
// Code: Implementation omitted
}
}
Next we will write a piece of code to test it:
Book [] bookarray = new book [2];
Book book1 = New Book (124, "C #3.0 secrets ");
Book book2 = New Book (45, ". Net beauty ");
Supercalculator calculator = new supercalculator ();
Calculator. speedsort <book> (bookarray );
Because the speedsort () method is not implemented, this Code does not have any output. If you want to see the output, you can simply paste the code in the above Bubble sorting, I will not demonstrate it here. Here I want to talk about an interesting compiler capability. It can infer the array type you pass and whether it meets the generic constraints. Therefore, the above speedsort () the method can also be called as follows:
Calculator. speedsort (bookarray );
In this way, although it is a generic method, its usage is no different from that of a common method.
1.1.4 Summary
In this section, we learned the most basic knowledge required to master generics. You can see the reason for the need for generics, which can avoid repeated code, we also learned how to use type parameter constraints and generic methods. With the knowledge of this section, you are sufficient to deal with most scenarios in daily development.
In the following two sections, we will continue to learn generics, including the application of generics in the Collection class and advanced topic of generics.