Let's take a look at the definition of the most common generic type List <T>.
(The real definition is much more complicated than this one. I deleted a lot of things here)
- [Serializable]
- public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>
- {
- public T this[int index] { get; set; }
- public void Add(T item);
- public void Clear();
- public bool Contains(T item);
- public int IndexOf(T item);
- public bool Remove(T item);
- public void Sort();
- public T[] ToArray();
- }
A List followed by a <T> indicates that it operates on an unspecified data type (T indicates an unspecified data type)
T can be considered as a variable name. T represents a type and can be used anywhere in the source code of List <T>.
T is used as the parameter and return value of the method.
The Add method receives T-type parameters. The ToArray method returns a T-type array.
Note:
Generic parameters must start with T, either T or TKey or TValue;
This is the same as the interface starting with I, which is the Convention.
Let's take a look at a piece of code that uses generic types.
- Var a = new List <int> ();
- A. Add (1 );
- A. Add (2 );
- // This is incorrect. Because you have specified the generic type as int, you cannot add other values in this container.
- // This is a compiler error, which improves the efficiency of troubleshooting. If it is a runtime error, I don't know how annoying it is.
- A. Add ("3 ");
- Var item = a [2];
Note the comments in the above Code
Ii. Effects of generics (1 ):
As a programmer, you do not forget to reuse code when writing code.
Code reuse can be divided into many types. algorithm reuse is a very important one. Suppose you want to write a sorting algorithm for a group of integer data and a sorting algorithm for a group of floating-point data, if there is no generic type, what would you do?
You may have thought of method overloading.
Write two methods with the same name. One method receives the integer array, and the other method receives the floating point array.
But with generics, you don't have to do this. You only need to design a method. You can even use this method to sort a group of string data.
Iii. Functions of generics (2 ):
Assume that you are a method designer. This method requires an input parameter, but you can determine the type of the input parameter. What will you do?
Some people may immediately refute: "This is not the case !"
Then I will tell you that programming is an experience-based job, and your experience is not enough, and you have never encountered any similar issues.
Another part may consider setting the parameter type to Object. This is indeed a feasible solution, but it may cause the following two problems, if I pass the integer data (the value type data is the same) to this method, additional packing and unpacking operations will be generated, resulting in performance loss.
If the processing logic in your method does not apply to string parameters, and the user uploads another string, the compiler will not report an error and will only report an error at runtime.
(If the Quality Control Department does not measure the BUG During the runtime, I don't know how much damage it will cause)
This is what we often say: the type is insecure.
Iv. Example of generics:
Generic types such as List <T> and Dictionary <TKey, TValue> are frequently used. Below I will introduce several generic types that are rarely used.
ObservableCollection <T>
When this set changes, corresponding events will be notified.
See the following code:
- Static void Main (string [] args)
- {
- Var a = new ObservableCollection <int> ();
- A. CollectionChanged + = a_CollectionChanged;
- }
-
- Static void a_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
- {
- // You can use Action to determine which operation triggers the event.
- // E. Action = policycollectionchangedaction. Add
-
- // You can obtain the pre-modification and post-modification content based on the following two attributes:
- // E. NewItems;
- // E. OldItems;
- }
To use this set, you must reference the following two namespaces:
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
BlockingCollection <int> is a set of thread security.
Let's take a look at the following code.
- Var bcollec = new BlockingCollection <int> (2 );
- // Try to add 1-50
- Task. Run () =>
- {
- // Parallel Loop
- Parallel. For (1, 51, I =>
- {
- Bcollec. Add (I );
- Console. WriteLine ("add:" + I );
- });
- });
-
- Thread. Sleep (1000 );
- Console. WriteLine ("call Take once ");
- Bcollec. Take ();
-
- // Wait for an infinite amount of time
- Thread. Sleep (Timeout. Infinite );
Output result:
- Join: 1
- Add: 37
- Call Take once
- Join: 13
BlockingCollection <int> can also set the CompleteAdding and IsCompleted attributes to reject new elements.
The. NET Class Library also provides many generic types, which are not described here.
V. Generic inheritance:
In. net, everything inherits the word Object, and generics are no exception. generic types can inherit from other types.
Let's take a look at the following code:
- public class MyType
- {
- public virtual string getOneStr()
- {
- return "base object Str";
- }
- }
- public class MyOtherType<T> : MyType
- {
- public override string getOneStr()
- {
- return typeof(T).ToString();
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- MyType target = new MyOtherType<int>();
- Console.WriteLine(target.getOneStr());
- Console.ReadKey();
- }
- }
The generic type mythertype <T> successfully overwrites the non-generic type MyType method.
If I try to derive a child type from the MyOtherType <T> type as follows, it will cause a compiler error.
- // Compilation Error
- Public class MyThirdType: MyOtherType <T>
- {
- }
-
However, if you write this method, no errors will occur.
- public class MyThirdType : MyOtherType<int>
- {
- public override string getOneStr()
- {
- return "MyThirdType";
- }
- }
Note:
If you follow the preceding method, the types may be inconsistent,
If a method receives parameters of the MyThirdType,
Therefore, you cannot pass a MyOtherType <int> instance to this method,
However, if a method receives a parameter of the mythertype <int> type,
However, you can pass the MyThirdType instance to this method,
This is caused by the CLR internal implementation mechanism,
This looks really weird!
The following method does not cause errors:
- public class MyThirdType<T> : MyOtherType<T>
- {
- public override string getOneStr()
- {
- return typeof(T).ToString() + " from MyThirdType";
- }
- }
The trick is not to mention it.
6. Generic Interfaces
. NET class library has many generic interfaces, such as IEnumerator <T> and IList <T>. These interfaces are not described in detail here, it indicates why generic interfaces are required.
In fact, the reason for the appearance of generic interfaces is similar to that for the appearance of generic interfaces. For IComparable interfaces, this interface only describes one method:
- int CompareTo(object obj);
As you can see, if it is a value type parameter, it will inevitably lead to packing and unpacking operations.
At the same time, it is not a strong type, and the parameter type cannot be determined during the compilation period. With IComparable <T>, this problem is solved:
- int CompareTo(T other);
VII. Generic Delegation
Delegation description method. The origins of generic delegation are similar to those of generic interfaces.
Defining a generic delegate is also relatively simple:
- public delegate void MyAction<T>(T obj);
This delegate describes a type of methods that receive T-type parameters without returning values.
Let's see how to use this delegate:
- public delegate void MyAction<T>(T obj);
- static void Main(string[] args)
- {
- var method = new MyAction<int>(printInt);
- method(3);
- Console.ReadKey();
- }
- static void printInt(int i)
- {
- Console.WriteLine(i);
- }
Due to the cumbersome definition of delegation, the. NET class library defines three common generic delegation types in the System namespace.
Predicate <T> delegate:
- public delegate bool Predicate<T>(T obj);
The method described in this delegate is to receive a T-type parameter and return a BOOL-type value, which is generally used for comparison.
Action <T> delegate
- public delegate void Action<T>(T obj);
- public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
The method described by this delegate receives one or more T-type parameters (up to 16, I only wrote two types of definition methods here) without returning values.
Func <T> delegate
- public delegate TResult Func<TResult>();
- public delegate TResult Func<T, TResult>(T arg);
The method described by this delegate receives zero or multiple T-type parameters (up to 16, I only write two types of definition methods here ), different from the Action delegate, it has a return value, and the return value type is TResult.
For more information about delegation, see my article.
VIII. Generic Method
T in the generic type can be used anywhere in this type. However, sometimes we do not want to specify the T type when using the type, we want to specify the T type when using this type of method.
Let's take a look at the following code:
- public class MyClass
- {
- public TParam CompareTo<TParam>(TParam other)
- {
- Console.WriteLine(other.ToString());
- return other;
- }
- }
In the above Code, MyClass is not a generic type, but CompareTo <TParam> () in this type is a generic method, and TParam can be used anywhere in this method.
You can use the following code to use the generic method:
- obj.CompareTo<int>(4);
- obj.CompareTo<string>("ddd");
However, you can write more simply by writing as follows:
- obj.CompareTo(2);
- obj.CompareTo("123");
Someone may ask, "This is impossible. If the TParam type of the CompareTo method is not specified, compilation errors will certainly occur"
I will tell you: no, the compiler can help you complete type inference.
Note:
If you specify two generic parameters for a method and both parameters are of the T type, if you want to use type inference, you must pass two parameters of the same type to this method. One Parameter cannot be of the string type, and the other must be of the object type. This will cause compilation errors.
9. Generic Constraints
We have designed a generic type. In many cases, we do not want users to input any type of parameters, that is, we want to "constrain" the type of T.
Let's take a look at the following code:
- public class MyClass<T> where T : IComparable<T>
- {
- public int CompareTo(T other)
- {
- return 0;
- }
- }
The above Code requires that the T type must implement the IComparable <T> interface.
As you can see, generic constraints are implemented by the where keyword.
Generic parameters can also be constrained in a similar way.
See the following code:
- public class MyClass
- {
- public TParam CompareTo<TParam>(TParam other) where TParam:class
- {
- Console.WriteLine(other.ToString());
- return other;
- }
- }
The above Code uses the class keyword to constrain the generic parameter TParam. The details will be explained later.
Note 1:
If I have a type that is also defined as MyClass <T> but not bound, then the MyClass that has been constrained <T> will conflict with the MyClass that has not been constrained <T>, compilation fails.
NOTE 2:
When you override a generic method, if the method specifies a constraint, you cannot specify the constraint when rewriting this method.
NOTE 3:
Although the preceding example describes interface constraints, you can write a type, for example, BaseClass. In addition, as long as the type inherited from BaseClass can be used as the T type, you should not try to restrict T to the Object type, and the compilation will not pass. (This is what fools do)
Note 4:
There are two special constraints: class and struct.
Where T: class constraints T type must be reference type
Where T: struct constraint T type must be Value Type
Note 5:
If you do not impose class constraints on T,
You cannot write such code: T obj = null; this cannot be compiled because T may be of the value type.
If you do not impose a struct constraint on T, there is no new constraint on T.
Then you cannot write the code like this: T obj = new T (); this cannot be compiled, because there must be no parameter constructor for the value type, and the reference type is not necessarily.
If you impose a new constraint on T: where T: new (); then new T () is correct, because the new constraint requires that T type has a common no-argument constructor.
Note 6:
Even if no constraints are imposed on T, there is a way to solve the problem of value type and reference type.
T temp = default (T );
If T is the reference type, temp is null; if T is the value type, temp is 0;
Note 7:
If you try to forcibly convert a variable of the T type, a compilation error is reported.
However, you can first convert T to an object and then convert the object to the type you want (this is generally not recommended. You should consider converting T to a Type compatible with constraints ).
You can also consider using the as operator for type conversion, which generally does not report an error, but can only be converted to a reference type.
The content of generic constraints is also mentioned in this article.
10. inverter and collaborative change
Generally, when we use generics, the generic types marked by T cannot be changed.
That is to say, both of the following statements are incorrect:
- var a = new List<object>();
- List<string> b = a;
- var c = new List<string>();
- List<object> d = c;
Note: There is no write forced conversion here. Even if the write forced conversion is incorrect, the compilation will fail. However, the generic type provides the inverter and collaborative transformation features. With these two features, this conversion is possible.
Inverter:
The generic type T can be changed from the base type to the derived type of the class. The in keyword is used to mark the type parameter in the invert format. This parameter is generally used as an input parameter.
Covariant:
Generic Type T can be changed from the derived type to its base type, and the out keyword is used to mark the type parameter of the covariant type. This parameter is generally used as the return value.
If we define a delegate like this:
- public delegate TResult MyAction<in T,out TResult>(T obj);
Then, the following code can be compiled (without forced conversion)
- var a = new MyAction<object, ArgumentException>(o => new ArgumentException(o.ToString()));
- MyAction<string, Exception> b = a;
This is the power of inverter and collaborative change.