C # constraints of functional programming every time a generic type is used, you can use the where clause to add constraints to the generic type: static void OutputValue <T> (T value) where T: listItem <string> {Console. writeLine ("String list value: {0}", value. value);} This example intuitively states a constraint: type T must match ListItem <string>. Generic Type constraint T: X indicates that T can be a derived object of X and X or an implementation of X (if X is an interface ). In other words, if an instance of type T is t, it can be assigned to a variable: X x X = t; constraints can use specific types, but in these cases, the type cannot be sealed. There are several special keywords that can replace or supplement the type declaration. The keyword class indicates that this type must be a reference type, and struct indicates that it must be a value type. When new () is used with a class or any specific type, you can define a default constructor for this type. The last application of the constraint defines the relationship between two types of parameters. For example, for the T and U types of parameters, the constraint T: U indicates that T must be compatible with U. When using constraints, remember that the basic function of generics is to provide a type-safe method so that code can process different types of data. The more constraints you use, the farther away from this idea, because constraints reduce flexibility. C # function Programming other generic types in addition to methods and classes, struct, delegate, and interface can also use type parameters. Struct and interface use type parameters are obvious, and their usage is similar to class: public struct MyPoint <T> where T: struct {public MyPoint (T x, T y) {this. x = x; this. y = y;} private readonly T x; public t x {get {return x ;}} private readonly T y; public t y {get {return y ;}} public interface IListItem <T> {T Value {get;} ListItem <T> Prepend (T value);} Even if it is a delegate, its usage is nothing surprising: public delegate R CreateDelegate <T, R> (T param); public class Pa RameterFactory <T, R> {CreateDelegate <T, R> createDelegate; public ParameterFactory (CreateDelegate <T, R> createDelegate) {this. createDelegate = createDelegate;} after using generics, these delegates can represent almost any function. C # The covariant and inverter of functional programming if an operation retains the original sequence of types, it becomes a covariant. If they are reversed, it is called an inverter. The so-called type order means that the sequence values of common types are stronger than those of special types. The following example shows that C # supports covariant. First, an object array is defined: object [] objects = new object [3]; objects [0] = new object (); objects [1] = "Just a string"; objects [2] = 10; different values can be inserted into this array, because all data is ultimately derived from.. NET Object type. In other words, an Object is a very common type, that is, it is a strong type. Next, we will explain. NET supports covariant. It assigns a weak type value to a strong type variable: string [] stringsTest = new string [] {"one", "two ", "three"}; objects = stringsTest; the objects variable belongs to the object [] type. It can save the value of the actual type as string. Think about it. We hope this is the case, but this is not the case. After all, although string is derived from object, string [] is not derived from object []. Even so, this assignment is feasible because C # in this example supports covariant. To illustrate the idea of inverter, a complicated example is required: public class Person: IPerson {public Person () {}} public class Woman: Person {public Woman () {}} Woman is a class derived from Person. The following two functions are analyzed: static void WorkWithPerson (Person person) {} static void WorkWithWonman (Woman woman) {} one function acts on the Woman class, and the other function is more common and acts on the Person class. Two delegates and functions can be defined from the Woman class: delegate void AcceptWomanDelegate (Woman person); static void DoWork (Woman woman, AcceptWomanDelegate acceptWoman) {acceptWoman (woman );} the DoWork function accepts a Woman parameter and a function reference, and the latter also accepts a Woman parameter. The DoWork function passes the Woman instance to the delegate. The element type is: Person is better than Woman, And WorkWithPerson is better than WorkWithWoman. In order to apply the inverter, it is considered that WorkWithPerson is better than AcceptWomanDelegate. See the following code: Woman woman = new Woman (); doWork (woman, WorkWithWonman); DoWork (woman, WorkWithPerson); Create a Woman instance, call the DoWork function, and pass the reference addresses of the Woman instance and WorkWithWoman method to DoWork. The latter is obviously compatible with the delegate type AcceptWomanDelegate-both have only one Woman type parameter and no return value. However, the third line of code is a bit strange. According to the requirements of AcceptWomanDelegate, The WorkWithPerson method accepts a Person parameter instead of a Woman parameter. Even so, WorkWithPerson is compatible with the delegate type, which is caused by the inverter. Therefore, under the delegate type, a strong type can be stored in a weak type variable. Variations can also be applied to generics. Code: List <object> objectList = new List <object> (); List <string> stringList = new List <string> (); objectList = stringList;