Set and generic, set generic
The Collection class is responsible for storing a series of individuals, and its set length may be unchanged or variable. Compared with the ordinary array structure, the functions of the Collection class are more specific.
Collection classes are divided into common (non-generic) sets and generic sets. The namespace of the Generic set class is a sub-namespace of the set: System. Collections. Generic. In a non-generic set, all members are treated as objects. Different members can have different data types. All member types in a generic set are the same.
Non-generic set
All non-generic sets implement the ICollection interface.
All members in a non-generic set are objects.
ICollection
All non-generic sets implement this interface. This interface inherits the IEnumerable interface, so it can be traversed. In addition, this interface provides the following attributes:
- Count: gets the number of Members contained in this set.
- AsParallel: allows parallel query
- AsQueryable: This class can be converted to IQueryable.
ICollection interface extends IEnumerable; IDictionary and IList are more dedicated interfaces for extending ICollection. IDictionary implements a set of key/value pairs, such as the Hashtable class. You cannot access an object that implements IDictionary by indexing. You can only provide one key and then check whether the value is a member of the key. A set of IList implementation values, whose members can access through indexes, such as the ArrayList class.
IList interface (non-generic version) and ArrayList
Compared with the abstract basic interface ICollection, IList is more specific. It provides add (insert end), insert (insert after a specified item), remove, removeat, clear and other basic inserts, delete. There are three ways to implement IList: Read-Only (cannot be modified), declared size and no declared size. Generally, no declared size.
ArrayList is a type that implements the IList interface. It is more flexible to insert and delete than Array. Array insertion is very troublesome, because if there is still data after it, you need to manually put all the subsequent data one by one. This issue does not need to be considered in ArrayList (or all types that implement the IList interface. This is the first difference between it and array. The second difference is that it is a non-generic set, so all its members are considered as objects. The third difference is that the ArrayList capacity is variable, so it will never overflow. When a new ArrayList is declared, if no member is inserted at the same time, its capacity is zero. When a member is inserted, the system automatically adjusts the ArrayList capacity to multiply from 4. That is, if five members are inserted at a time, the system adjusts the ArrayList capacity to 8, if only one member is inserted, the capacity is 4. Because we usually do not know the length of the array, we can use ArrayList to conveniently store data without worrying about overflow. When the members of ArrayList are smaller than the capacity, although the space is wasted, the number of space wasted is usually more economical than the array.
IList interface: other non-generic Sets
Other non-generic sets include queues and stacks. As the name suggests, they implement the data structure of queues and stacks. These data structures are usually of little use.
IDictionary interface and Hashtable
The hash table can be viewed as a non-generic version of the dictionary. It stores several key-value pairs and accesses them through keys. With generics, the efficiency of searching non-generic hash tables is far lower than that of generic dictionaries. Therefore, hash tables are rarely used.
SortedList
SortedList is special. Both Hashtable and SortedList indicate the set of key/value pairs, but hashtable does not sort, so it is faster to add elements. SortedList stores key-value pairs sorted by keys. To sort elements, you must first find the position of the elements before inserting them. This is relatively slow, however, the search speed is faster.
Thread-safe Collection class System. Collection. Concurrent [. NET 4.0 +]
The System. Collections. Concurrent namespace provides multiple thread security collection classes. When multiple threads concurrently access the set, these classes should be used to replace the corresponding types in the System. Collections and System. Collections. Generic namespaces. We use ConcurrentDictionary as an example. When multiple threads access ConcurrentDictionary, thread security ensures that Members are not added or deleted multiple times.
When we Add a key-Value Pair <a, B> to ConcurrentDictionary, we do not use Add, but use AddOrUpdate. If key a does not exist, the corresponding value B is inserted and returned. If key a exists, the value corresponding to the key is updated to B. Compared with the Dictionary and hashtable types, all operations are atomic and therefore thread-safe.
Generic set
Generic collections are all type-safe.
A Generic set belongs to the namespace System. Collections. Generic.
Advantages
C #2.0 introduces generics. It mainly solves the two disadvantages of non-generic sets. One is the packing and unpacking of non-generic sets during insertion and deletion, and the other is to prevent non-generic set types from being insecure, these problems cannot be found during compilation, but are only known during runtime. For example, if you have a non-generic ArrayList, you expect all of them to be integers. However, when you calculate the addition and subtraction, if one is a string, an exception occurs.
The declaration of generics requires a placeholder <T>. here T can be a type name or any other string. If it is a type name, such as List <string>, the List is type-safe, and all objects in the List are treated as strings. If it is only a non-Keyword string such as Tkey, the range of this type may be relatively large, such as including all value types. You need to use some method to let the compiler know where your type actually inherits. You can use the type constraints to help the compiler make inferences.
Similar to the interface structure of a non-generic set, almost all non-generic sets have their corresponding generic versions. ICollection <T> inherits IEnumerable <T>, and then IList <T> and IDictionary <T> inherit from him. List <T> and Dictionary <TKey, TValue> inherit the previous IList <T> and IDictionary <T> respectively. The inherited route is as follows:
IEnumerable <KeyValuePair (TKey, TValue)>-ICollection <KeyValuePair (TKey, TValue)>-IDictionary <TKey, TValue>-Dictionary <TKey, TValue>
IEnumerable <T>-ICollection <T>-IList <T>-List <T>
Generic methods and type constraints
Generic methods are input or output methods with generics. When you have many methods that are exactly the same (just different types of input and output), you can consider writing a generic method to unify it. For example, you want to write a method to compare the size of any two objects of the same type:
Public static T GetBiggerOne <T> (T itemOne, T itemTwo) { If (itemOne. CompareTo (itemTwo)> 0) { Return itemOne; } Return itemTwo; } |
However, this code is problematic. Because CompareTo is not defined for all types. It makes sense only for the type that implements the interface IComparable. Therefore, we need to limit T to implement IComparable. We can use type constraints.
The format of the type constraint is where T: type name or other constraints. There are several types of constraints:
1 Reference/value type constraints: When followed by where T: class, T must be a reference type. When followed by where T: struct, T must be a value type. This constraint must be the first one (from large to small ). Note that the reference and value type constraints cannot be set at the same time.
2. Other type constraints: the type name is followed by multiple types. Only one class can be specified (c # does not support multi-inheritance, so it is impossible to inherit two classes by type. Therefore, specifying more than one class will never meet the constraints ), however, you can specify multiple interfaces (the only method to implement multi-inheritance is to use interfaces ).
3. constructor constraint: where T: new (). At this time, T must have a constructor without parameters. Note that this constraint must be the last one. Note that you cannot write a constraint that has a parameter constructor. For example, where T: new (string) is invalid.
To run the code, you must add the following constraints:
Public static T GetBiggerOne <T> (T itemOne, T itemTwo) where T: IComparable { If (itemOne. CompareTo (itemTwo)> 0) { Return itemOne; } Return itemTwo; } |
Type inference
When a generic method is available, you do not need to specify a type when calling this generic method externally. the compiler will help us deduce the type. For example, you can run either of the following methods.
GetBiggerOne (1, 2 ); GetBiggerOne <string> ("a", "B "); |
Generic Delegation
With generic delegation, you can define the delegation without delegate. In other words, func AND action take over all the work. Generally, generic delegation is used together with anonymous methods or lambda expressions. This will greatly simplify the code writing workload of the delegate.
Action: there can be 0-16 inputs and no output
Func: the input can contain 0 to 16 characters and one output.
Predicate: the input is a set of conditions with a Boolean output, which is usually used as input by other methods, such as list <T>. FindAll
Generic covariant and Inverter
The definition of variability is to convert one type to another in a safe way. Generally, these two types have an inheritance relationship. Covariance: the conversion direction is from the derived type to the base type. The opposite is true for inverters. Objects that do not have the characteristics of covariant and inverter are called immutable. Note that this is different from the immutable character string.
Purpose of covariant and Inverter
Consider the following simple interface, which returns an instance for any type:
Interface GetInstance <T> { T Create (); } Public class Aclass: GetInstance <string> { // String factory Public string Create () { Return ""; } } |
At this point, T is just a return value, which means we can regard a specific type of factory as a general factory. In this case, we use Covariance, which converts a smaller value (range) to a larger value.
Inversion is the opposite, which means T is an input value, and the interface will consume it instead of generating it. The following interface consumes T because T appears in the interface signature as a parameter. Therefore, if we implement Print <object>, We can Print everything theoretically. In this case, the inverter is used to convert a large value to a small value.
Interface Print <T> { Void Print (T input ); } Public class Bclass: Print <object> { Public void Print (object o) { Console. WriteLine (o. ToString ()); } } |
When the above two events occur at the same time, this interface will automatically lose the features of the covariant and inverter, and become a constant body. In c #4.0, we use the out and in keywords to achieve covariant and inverter. This feature can only be used for interfaces and delegation.
When declared"Out"It indicates that it is used to return, and can only be returned as a result, but cannot be changed in the middle.
When declared"In"It indicates that it is used for input. It can only be used as a parameter input and cannot be returned.
IEnumerable <T> covariance (out)
In syntax, you cannot implicitly convert list <string> to list <object>.
List <string> aa = new List <object> (); |
This is because although string inherits objects, list <string> and list <object> do not have an inheritance relationship. Likewise, the redirection is wrong.
List <object> bb = new List <string> (); |
However, this is correct:
IEnumerable <string> aa = new List <string> (); IEnumerable <object> bb = aa; |
This is because IEnumerable <T> has a covariance and its signature is:
Public interface IEnumerable <out T>: IEnumerable |
Do you see the out keyword? This keyword gives IEnumerable <T> the feature of covariant.
IComparer <T> inversion (in)
The class that implements the IComparer interface <T> can compare the size. This is common sense. Obviously, if T is a parent class, any subclass of T can be passed in. This is inverter. The interface signature is:
Public interface IComparer <in T> |
Similar. net interfaces and delegation include:
Interface:
- IQueryable <out T>
- IEnumerator <out T>
- IGrouping <out TKey, out TElement>
- IEqualityComparer <in T>
- IComparable <in T>
Delegate:
- System. Action <in T>
- System. Func <Out Tresult>
- Predicate <in T>
- Comparison <in T>
- Converter <in TInput, out TOutput>
Application
We can also use covariant and inverter when defining generic interfaces. Let's look at an example to reflect the characteristics of covariant.
Interface <out T> {T property {get; set ;}}
I define an interface, an attribute with get and set accessors. However, an error is reported during compilation, prompting that the variant is invalid: The type parameter "T" must be for "test. interface <T>. property "valid and fixed. "T" is a covariant. Because I declared T as a covariant,TCan only be returned and cannot be modifiedSo, if the "set" accessors are removed, they can be compiled and passed. Similarly, if I declare a method in the "interface": The void method (T t) also reports an error, T is declared as a covariant, and "method (T t).
Example of inverter:
Interface <in T> {void method (T t);} class <T>: interface <T> {public void method (T t ){}}
The statement "in" cannot be returned, but can be changed.
Static void Main (string [] args) {interface <car> A group of cars = new <car> (); interface <car> A group of cars = a group of cars ;}
Because the "interface" declares the "in" keyword and is declared as an inverter, the parent car can now be converted to a subclass car. Accepting a parameter with a type that is relatively "weaker" is actually a process that makes the type of a parameter more concrete and clearer.