What are Generics?
In C#, generics are used to create classes, methods, structs and other components that are not specific, but general. This allows us to use the generic component for different reasons. Generics give us some extra power of reusability in our code, which is good for an application as there would be less code which does similar work. Generics are not newly developed; they has been available since C# 2.0.
The angle brackets <>, next to the Price class is the syntax of generics in C#. By putting <> next to the class name, we are telling the compiler that this is a generic class. Furthermore, the T inside <> is a type parameter. A type parameter is like any other parameter in C# programming, except it passes a type instead of a value or reference. We created a generic Price class. To make it generic, we placed next to the class name. Here, the T is a type parameter, but it’s not something fixed that you have to use T with to represent the type parameter you can use anything to represent it. However, it is traditional to use T for the type parameter. If there are more type parameters, V and E are used. There is another popular convention when using two or more parameters, which is to name the parameter something such as TValue and TKey, instead of just V and E,
which is done for better readability. However, as you can see, we have prefixed T before the words Value and Key, which is done to distinguish between a type parameter and a general parameter.
When we run the preceding code, the type that we pass in the class will be the type of the object ob. Consequently, we can say that T is a placeholder, which will be replaced with some other concrete C# types (int, double, string, or any other complex type) in the runtime.
In the constructor, we passed a parameter of the T type and then assigned the value of the passed parameter, o, to the local variable, ob. We can do this assignment as the parameter passed in the constructor is also the T type.
Why do we need generics?
The object type can be used for any type in C#, and the preceding example can be achieved through the use of an object type. Yes, the preceding example can be achieved through the use of the object type, but there won’t be any typesafety. In contrast, generics ensure that the type-safety is there when the code gets executed. Type safety actually refers to keeping the type secure or unchangeable when executing any task in the program. This helps us reduce runtime errors.
Different constraints of generics?
There are different types of constraints available in C# generics:
Base class constraints: The idea of this constraint is that only the classes that extend a base class can be used as generic type. For example, if you have a class named Person and you use this Person class as a base for the Generic constraint, only the Person class or any other class that inherits the Person class can be used as the type argument for that generic class.
Interface constraints: Similar to the Base class constraint, we see the interface constraint when your generic class constraint is set as an Interface. Only those classes can be used in the generic method that implements that interface.
Reference type and value type constraints: When you want to differentiate between your generic class and reference types and value types, you need to use this constraint. When you use a Reference type constraint, the generic class will only accept the Reference type objects.
To achieve that, you have to extend your generic class with a class keyword. Furthermore, when you want to use a value type, you have to extend your generic class with a ‘value type’ keyword. So, when you make a value type constraint, this means that the generic will only work for value types such as int or double. No reference type, such as string or any other custom class, will work.
Like the Generic class, there can be generic methods, and a generic method does not necessarily have to be inside a generic class. A generic method can be inside a non generic class as well. To create a generic method, you have to place the type parameter next to the method name and before the parenthesis. The general form is given here:
Here, we can see that the Hello class is not a Generic class. However, the Larger method is a generic method. This method takes two parameters and compares them, returning the larger value. This method has also implemented a constraint, which is IComparable. In the main method, we have called this generic method several times, once with int values and once with double values. In the output, we can see that the method was successfully able to compare and return the larger value.
In this example, we have used only one type of parameter, but it is possible to have more than one parameter in a generic method. We have also created a static method in this example code, but a generic method can be non static as well.
The Compiler is getting smarter, the previous code is an example of a type-inferencing in a generic method. Type-inferencing means calling a generic method without specifying the type parameter, and letting the compiler identify which type to use. This is because the compiler used type inferences to figure out the type of arguments that were passed in the methods and executed the method as if the parameter type was already given to the compiler. Because of that, when you use a type inference, it’s not allowed to provide different types of arguments in a generic method. If you need to pass different types of arguments, you should explicitly do that. You can also apply the constraints on a method that can be applied on the classes as well.
Covariance and contravariance in generics
However, from C# 4, these are also available for generic interfaces and delegates, the concepts of covariance and contravariance in generics is almost the same as it is in delegates.
Covariance: This means that the generic interface that has a T type parameter can return T or any class that is derived from T. To achieve this, the parameter should be used with the out keyword.
Contravariance: The word “Contravariance” might sound a little complex, but the concept behind it is very simple. Normally, when creating a generic method, the argument we pass to it is the same type as T. If you try to pass another type of argument, it will give you a compile-time error. However, when using contravariance, you can pass the base class, which the type parameter implements. In addition, to use contravariance, there is a special syntax we have to follow.
We will see that we have created an Interface named IFood, which uses contravariance, this means that if this interface is implemented in a generic class, that class will allow the base class of the provided type parameter. The T in the IFood interface method signature is used as a parameter in the method. Now, a class named HealthyFood implements the interface, and the method that is implemented in the class only prints a string. Then, we created two classes: Vegetable and Carrot. Carrot extends Vegetable. Both classes override the ToString() method, and return Carrot if the class is Carrot or Vegetable if the class is Vegetable.
In the main method, we create an object of the Carrot class and an object of the Vegetable class. Both of these are kept in the IFood variable. The interesting part here is that the mySelf2 variable is of the IFood type, but it holds an object of the HealthyFood type. This is only possible because of contravariance. If you remove the in keyword and try to run the program again, you will fail and the compiler will throw an error to say that this is not possible. It was only possible to run the code because of contravariance.