Introduced
Generics are created to create a function that takes effect on all data types. The most common example is the operator, after all, 1+1=2, 1.0+1.0=2.0, which is enough to see that + works on multiple data types.
However, if you want to create a function add(int a, int b), then you will definitely report an error when entering add(1.0, 1.0), and VS will directly mark it red.
The emergence of generics solves this embarrassing problem very well
T add<T>(T a, T b) { dynamic d1 = a; dynamic d2 = b; return (T)(d1 + d2); } (add<int>(1, 1)); (add<double>(1.0, 1.0));
In the above code, T represents a certain data type. When calling the function add, it is declared according to the <> after add.
But it is obviously not possible to write return a+b just now, because the + operator does not overload T, and the editor will not allow two unknown types to be added.
At this time, dynamic is needed to make the editor give up type checking and leave any possible errors to the run stage.
Finally, the run result is
2
2
Type constraints
Dynamic is really good to use, but the consequence is that you are responsible. If this thing is used in teamwork, it will be a disaster. After all, not all objects can control addition.
Therefore, C# generics are generics that can be constrained. The key is where, write the above code as
T add<T>(T a, T b) where T : struct{ dynamic d1 = a; dynamic d2 = b; return (T)(d1 + d2); }
where T : struct means that T must be a type of numerical type, so the compiler's type check will still work. When calling add, if T is not a numerical type, an error will be reported.
There are 5 constraint schemes in C#, the list is as follows
category | condition |
---|---|
struct | T must be a value type |
class | T must be a reference type |
new() | T must have a constructor with no parameters |
Base class name | T must be a base class or derived from a base class |
Interface name | T must be a specified interface |
Nude type |
Different types of constraints, or the same type of constraints, can generally be mixed. If they cannot be mixed, the compiler will remind you. For example, struct can hardly be mixed with other types. If new() participates in the constraint, it is placed at the end.
Subclass generics
In addition to functions that can use generics, classes can of course be also possible, not only can they be inherited, but also can be inherited.
class MyList<T> { public T[] a; public MyList(){} //Constructor without parameters, used for inheritance public MyList(int n){ a = new T[n]; } public T this[int index]{ get => a[index]; set => a[index] = value; } }
MyList is equivalent to putting a shell on the array, and its constructor does not have any difficult to understand. The only problem may be the following index, public T this[int index], which can implement square bracket indexing.
Can test it
var a = new MyList<int>(5); for (int i = 0; i < 5; i++) { a[i] = i; (a[i]); }
As a result, I won't paste it, and then create a new subclass
class MyStack<T> : MyList<T> { public MyStack(int n) { a = new T[n]; } public T Pop() { T p = a[- 1]; a = a[0..(-1)]; return p; } }
Then test it
var a = new MyStack<int>(3); for (int i = 0; i < 3; i++) { a[i] = i; } for (int i = 0; i < 3; i++) { (()); }
The result is
2
1
0
Commonly used generic data structures
C# defines many data structures through generics, such as the dictionary mentioned when explaining switch...case
Dictionary<int, string> card = new Dictionary<int, string> { {1,"A" }, {11, "J" }, {12, "Q" }, {13, "K" } };
This way of writing <U, V> is exactly the characteristic of generics, where U, V are variables that can be declared at will. If you look at the type parameters of the dictionary, you can find that the definition method is like this
public class Dictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, ... where TKey : notnull
Considering that this section is not intended to be object-oriented, the large class inherited by the dictionary is omitted. The key is where Tkey:notnull, that is, there is only one requirement for key-value pairs, that is, the key must not be null.
In addition to dictionaries, there are also some common data structures that use generics. The list is as follows. You can practice if you have nothing to do.
Data structure | illustrate | Common methods |
---|---|---|
List<T> | Generic List | Add, Remove, RemoveAt |
LinkedList<T> | Double-ended link list | AddFirst, AddLast, RemoveFirst, RemoveLast |
Queue<T> | First-in First-out list | Enqueue, Dequeue |
Stack<T> | Stack, first in and then out | Push, Pop |
Generic Delegation
Delegation is a function of a function; generics can make the parameter types of a function more flexible. Combined with the two, it is a more flexible function function, namely a generic delegate.
As long as you have learned generics and delegates, you will have no difficulty in understanding generic delegation. Recalling the operator delegation defined earlier
delegate int Op(int a, int b);
Thinking back to the <T> when defining generics, then the generic delegation can be defined very simply.
delegate T Op<T>(T a, T b);
Then, you can create a generic function based on the delegate
T add<T>(T a, T b) { dynamic d1 = a; dynamic d2 = b; return (T)(d1 + d2); } var addTest = new Op<int>(add<int>); //You can also omit the <int> after add and write it in the following form//var addTest = new Op<int>(add); (addTest(3, 5));
After running, 8 appears on the console, which is that simple.
This is the end of this article about the detailed explanation of generics of C# advanced static language efficiency tools. For more related C# generic content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!