Generic type
Generics are much more grounded than reflection, delegation, and so on, and in normal work we have contact with generics almost every moment. Most people are more familiar with generics.
A generic collection is a collection of type-safe. We have type unsafe collection system.collections, relative to generic System.Collections.Generic, where members are of type object. A classic example is the ArrayList.
When using ArrayList, we can insert any type of data, and if you insert data of a value type, it will be boxed to the object type. This causes the type to be unsafe and we do not know whether the extracted data is the desired type. The data types of generics (collections) are uniform, are type-safe, have no boxing and unpacking problems, and provide better performance. Setting a default value for a generic variable is often done using the default keyword: T temp = default (t). If T is a reference type, temp is null, and if T is a value type, temp is 0.
The generic collection version of ArrayList is list<t>. T is called a type parameter. The specific type specified at the time of invocation is called the actual parameter (argument).
Interview must know the three major benefits of generics: type safety, enhanced performance, code reuse.
The use of generic collections: At almost any time, the substitution of generic collections for generic collections is not considered. Many non-generic collections also have their own generic versions, such as stacks, queues, and so on.
Generic methods
The use of generic methods can generally be used for incoming types, but they are handled in the same way. Instead of having to write many overloads, consider using a generic method to achieve code reuse. With generic constraints, you can write more rigorous methods. Generic delegates can also be seen as an application of generic methods.
For example, exchange values for two variables of the same type:
Static void Swap<t> (refref T rhs) { T temp; = LHS; = RHS; = temp;}
Generic constraints
The purpose of a constraint is to limit the number of types that can be specified as generic arguments (that is, the specific type of T). By limiting the number of types, you can perform more operations on these types. For example, the following method, T is constrained to be a type that implements the IComparable interface. At this point, the incoming T has an additional CompareTo method in addition to the object type method. Since it is guaranteed that the incoming T must be a type that implements the IComparable interface, it is certain that the T type must contain the CompareTo method. If the constraint is removed, O1 has no CompareTo method.
Static int where T:icomparable<t>{ return O1.compareto (O2);}
If the object type data is passed into the method, an error is displayed. Because object does not implement the Icomparable<t> interface.
Generic constraints fall into the following categories:
- Interface constraint: A generic argument must implement an interface. Interface constraints can have more than one.
- Base type constraint: The generic argument must be a derived class of a base class. In particular, you can specify t:class/t: struct, where T can be a reference type or a value type, respectively. The base type constraint must precede other constraints.
- Constructor new () constraint: The generic argument must have an accessible parameterless constructor (the default is also available). The new () constraint appears at the end of the WHERE clause.
If the generic method does not have any constraints, the passed-in object is treated as an object. They are more limited in function. You cannot use the! = and = = Operators because these operators are not guaranteed to be supported by a specific type parameter.
Covariance and Contravariance
variability is the use of one object as another object in a type-safe manner. The corresponding term is invariant (invariant).
Variability
variability is the use of one object as another object in a type-safe manner. For example, variability in common inheritance: If a method declares a return type of stream, a MemoryStream can be returned when implemented. There are two types of variability: covariance and contravariance.
Covariance: You can create a variable of a more general type and assign it a value, which is a variable of a particular type. For example:
string " Test " ; // object obj = str;
Because string is definitely an object, this change is quite normal.
Inverse degeneration: In the above example, we cannot equate Str with a new object object. If forced to do so, it can only do this:
string s = (stringnewobject();
However, this will still make an error at run time. This also tells us that the inverse degeneration is very abnormal.
Covariance and contravariance of generics
co-degeneration and out keyword, used to return the value of an action to the caller. For example, the following interface has only one method, which is to produce an instance of type T. Then we can pass in a specific type. If we can treat ifactory<pizza> as ifactory<food>. This also applies to all subtypes of food. (It will be considered a more general type of implementation)
interface ifactory<t> { T CreateInstance (); }
Inverse, in contrast, is used in conjunction with the in keyword, which means that the API will consume values rather than production values. At this point the generic type appears in the parameter:
interface iprint<t> { void Print (T value);
This means that if we implement the IPRINT<CODE>, we can use it as iprint<csharpcode>. (It will be considered a more specific type of implementation)
If there is a bidirectional pass, nothing will happen. This type is non-variant (invariant).
Interface Istorage<t> { byte[] Serialize (T value); T Deserialize (byte[] data);
This interface is not variant. We cannot see it as a more specific or more general type of implementation.
Assume the following inheritance relationship people–> teacher,people–> Student.
If we use it in a covariant way (assuming you've built an instance of istorage< Teacher > and treated it as istorage<people>), we might have an exception when calling serialize. Because the Serialize method does not support covariance (if the parameter is another subclass of people, such as student, istorage< Teacher > will not be able to serialize student).
If we use it in an inverse way (assuming you've built an instance of istorage<people> and treated it as istorage< Teacher >), we might have an exception when calling deserialize. Because the deserialize method does not support contravariance, it can only return people and cannot return teacher.
Using in and out to represent variability
If the type parameter is used for output, the out is used, and in if it is used for input. Note that covariance and contravariance are embodied in derived classes of generic classes T and T. Currently the out and in keywords can only be used in interfaces and delegates.
Ienumerable<out t> Support Covariance
Ienumerable<t> supports covariance, which allows a signature similar to the following
void Method (ienumerable<t> anienumberable)
Method that passes in a more specific type (the derived class of T), but inside the method, the type is treated as ienumerable<t>. Note the Out keyword.
The following example demonstrates covariance. We use ienumerable<t> covariance to pass in more specific types of circle. The compiler sees it as a more abstract type shape.
Public classProgram { Public Static voidMain (string[] args) { varCircles =NewList<circle> { NewCircle (NewPoint (0,0), the), NewCircle (NewPoint (Ten,5), -), }; varList =NewList<ishape>(); //covariance of generics://AddRange Incoming is a special type of list<circle>, but the requirement is generic type list<ishape>//AddRange method signature: void AddRange (ienumerable<t> collection)//ienumerable<out t> allow covariance (covariance is especially important for LINQ, because many APIs are represented as ienumerable<t>)list. AddRange (circles); //This is the only way to do this before C # 4.0List. AddRange (circles. Cast<ishape>()); } } Public Sealed classCircle:ishape {Private ReadOnlyPoint Center; PublicPoint Center {Get{returnCenter;} } Private ReadOnly Doubleradius; Public DoubleRadius {Get{returnradius;} } PublicCircle (Point Center,intradius) { This. Center =Center; This. Radius =radius; } Public DoubleArea {Get{returnMath.PI * RADIUS *radius;} } } Public InterfaceIShape {DoubleArea {Get; } }
Icomparer<in t> supports inverse degeneration
IComparer supports inverse degeneration. We can simply implement a method that compares any plot area, and the incoming input type (in) is the most general type of ishape. Then, when used, the result we get is a more specific type of circle. Because any graph can compare area, circle of course also can.
Note that the IComparer signature is public interface Icomparer<in t>.
Public classProgram { Public Static voidMain (string[] args) { varCircles =NewList<circle> { NewCircle (NewPoint (0,0), the), NewCircle (NewPoint (Ten,5), -), }; //the inverse of a generic type://Areacomparer can compare the area of arbitrary plots, but we can pass in specific shapes such as circles or squares//Compare Method Signature: Compare (IShape x, ishape y)//icomparer<in t> supports inversion//The incoming circle circle, but the required input is IShapeCircles. Sort (Newareacomparer ()); } } classAreacomparer:icomparer<ishape> { Public intCompare (IShape x, IShape y) {returnX.area.compareto (Y.area); } }
Limitations of generic variability in C #
1. The variability of the type parameters of the class is not supported. only interfaces and delegates can have variable type parameters. The In and out modifiers can only be used to decorate generic interfaces and generic delegates.
2. Immutability only supports reference conversions. Immutability can only be used for reference types, prohibit any value types and user-defined conversions, as the following conversions are not valid:
- Convert ienumerable<int> to ienumerable<object>--boxing conversion
- Convert ienumerable<short> to ienumerable<int>--value type conversion
- Convert ienumerable<string> to ienumerable<xname>--user-defined conversion
3. The type parameter uses out or ref to disallow variability. For generic type parameters, if you want to pass an argument of that type to a method that uses the out or ref keyword, you do not allow variability, such as:
Delegate void somedelegate< in t> (ref T T)
This code compiler will make an error.
4. The variability must be explicitly specified. From the implementation of the compiler can fully determine which generic parameters can be contravariant and covariant, but actually did not do so, because the C # development team believes that: the developer must explicitly specify the variability, because it will encourage developers to consider the consequences of their behavior, to think about their design is reasonable.
5. Multicast delegation and variability cannot be mixed. The following code can be compiled, but will throw a ArgumentException exception at run time:
func<string""; Func<objectnewobject(); Func<object> Combined = objectfunc + Stringfunc;
This is because the Delegate.combine method that is responsible for linking multiple delegates requires that the arguments must be of the same type , while the output of the two generic delegates above is a string and the other is object. The above example can be modified to the correct code as follows:
func<string""; Func<objectnew func<object>(stringfunc); Func<objectnewobject(); Func<object> Combined = objectfunc + defensivecopy;
At this point, the output of the two generic delegates is object.
The interaction between covariance and contravariance
In the following code, there is a method in interface Ibar that accepts another interface IFoo as a parameter. IFoo is a support for co-change. This can cause a problem.
interface ifoo< in t> { } interface ibar< in T> { void Test (ifoo<t> foo); }
Suppose T is a string type. If there is a class of bar <t>: Ibar<t>, another class of Foo<t>:ifoo<t> an instance of bar should be able to invoke the method: Abar.test (foo).
classBar<t>: ibar<t> { Public voidTest (ifoo<t>foo) { Throw Newnotimplementedexception (); } } classFoo<t>: ifoo<t> { } classProgram { Public Static voidMain () {Bar<string> Abar =Newbar<string>(); Foo<Object> foo =Newfoo<Object>(); Abar.test (foo); } }
When the method is called, the parameter type passed in is foo<object>. Let's look at the signature of the method:
Interface ibar< in t> { void Test (ifoo<t> foo); }
Now our Abar type parameter T is string, so the incoming type of the test method we expect should also be ifoo<string>, or it can change to a type of ifoo<string>, but it is an object. Therefore, the methods of the two interfaces are problematic.
Interface ifoo< out t> { }
The problem is solved when the signature of the IFoo interface is replaced with the out modifier. At this time, due to allow the inverter,foo<object> can be changed into ifoo<string>. But I have short-sighted, and have not yet found this feature in the actual work of what application.
Resources
Http://www.cnblogs.com/LoveJenny/archive/2012/03/13/2392747.html
Http://www.cnblogs.com/xinchufa/p/3524452.html
Http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html
. NET Surface question Series [8]-Generics