This tutorial will briefly introduce you to the term and features of C-Sharp Generics.
The word Generic means not specific.
The C# programming language allows users to define classes, methods, static methods, interfaces, abstract classes, properties, events, delegates, and operators.
The feature of C# defines classes and methods without defining the data types during the definition of the class called Generics.
Generics help in improving the code reusability, type safety and have better performance as it does not require boxing, unboxing, and typecasting of variables and objects.
Generics are easy to implement and provide cleaner code for understanding and execution.
Generics were introduced as a part of .Net Framework 2.0 and it is available under the namespace System.Collections.Generic.
A generic class can be a base class as well as a derived class.
- Reusability — generic type definition can be used for multiple purposes;
- Type safety — generic provides options to use multiple data types for achieving the same purpose;
- Performance — generics provide better performance over normal system types as they reduce the need for typecasting, boxing, and unboxing;
A generic class can be a base class as well as a derived class.
The angular brackets represent that the class/method is a generic type, and the compiler will replace all the placeholders with the data type provided.
Generic classes are defined using class names followed by type parameters inside angular brackets.
You can see the defined MyGenericsClass source code:
1 | class MyGenericsClass<T> { } |
In the above example, MyGenericsClass is the class name and T is the type parameter.
The type parameter can be used for the fields, properties, return types, and delegates inside the class.
1 | class MyGenericsClass<T> { public T Info { get; set; } } |
See the generic class using multiple parameters, TKey and TValue are type parameters:
1 | class MyGenericsMultipleParameters<TKey, TValue> { } |
The type parameter that is being used in the class will be replaced with the data type string
Let’s instantiated the generic class with data type as a string:
1 | MyGenericsClass<string> MyGenericsClass = new MyGenericsClass<string>(); |
This shows the code allows the properties to be accessed using the object that is instantiated.
1 | MyGenericsClass.Info = "This is a test string"; |
Generic classes can also be used as a Base/Derived class:
You can see the generic derived class is assigned with the type as a string:
1 | class MyDerivedClass : MyGenericsClass<string> { } |
Generic methods can be used within a non-generic class.
1 2 3 4 5 6 7 | class MyDerivedClass<T> : MyGenericsClass<T> { } class MyGenericsClass<T> { public void WriteMessage<T>(T inputData) { Console.WriteLine(inputData); } } |
I can specify the type of type parameter explicitly while calling the method:
1 2 3 4 5 6 7 8 9 | class MyGenericClass { public void WriteMessage<T>(T inputData) { Console.WriteLine(inputData); } } var MyGenericClass = new MyGenericClass(); MyGenericClass.WriteMessage("This is a text!"); MyGenericClass.WriteMessage(1973); |
The .NET provides several generic classes and interfaces in System.Collections.Generic namespace …
- HashSet<T>
- LinkedList<T>
- List<T>
- Queue<T>
- Stack<T>
- ICollection<T>
- IComparer<T>
- IEnumerable<T>
- IEnumerator<T>
- ILIst<T>
Generic Constraints are validations that we can put on the generic type parameter.
There are six types of constraints.
- where T: struct – Type argument must be a value type
- where T: class – Type argument must be a reference type
- where T: new() – Type argument must have a public parameterless constructor.
- where T: <base class> – Type argument must inherit from <base class> class.
- where T: <interface> – Type argument must implement from <interface> interface.
- where T: U – There are two types of arguments T and U. T must be inherited from U.
You can see one example with the usage of the above constraints in Generic methods.
1 2 3 4 5 6 7 8 9 10 11 | static void MyGenericsMultipleParameters<T>(ref T inputData1, ref T inputData2) where T: struct { } static void MyGenericsMultipleParameters<T>(ref T inputData1, ref T inputData2) where T: class { } static void MyGenericsMultipleParameters<T>(ref T inputData1, ref T inputData2) where T : new() { } static void MyGenericsMultipleParameters<T>(ref T inputData1, ref T inputData2) where T: ABaseClass { } static void MyGenericsMultipleParameters<T>(ref T inputData1, ref T inputData2) where T: I_AInterface { } static void MyGenericsMultipleParameters<T, U>(ref T inputData1, ref U inputData2) where T: U { } |
Obviously, the source code is presented only to highlight the implementation method.