Generics is a very interesting way of writing code. Instead of specifying the data type of the elements in the code at design time, you can actually delay the specification of those elements until they are used in code. This basically means that your class or method can work with any data type.
We will start off by writing a generic class that can take any data type as a parameter in its constructor and do something with it.
<T>
:public class PerformAction<T> { }
The generic type parameter is basically a placeholder for a specific type that will need to be defined when the class of variable is instantiated. This means that the generic class PerformAction<T>
can never just be used without specifying the type argument inside angle brackets when instantiating the class.
private
variable of the generic type parameter T
. This will hold the value we pass to the generic class:public class PerformAction<T> { private T _value; }
T
. The private variable _value
will be set to the parameter passed to the constructor:public class PerformAction<T> { private T _value; public PerformAction(T value) { _value = value; } }
IdentifyDataType()
. All that this is going to do is tell us what data type we passed to the generic class. We can find the type of the variable using GetType()
:public class PerformAction<T> { private T _value; public PerformAction(T value) { _value = value; } public void IdentifyDataType() { WriteLine($"The data type of the supplied variable is {_value.GetType()}"); } }
PerformAction<int> iAction = new PerformAction<int>(21); iAction.IdentifyDataType(); PerformAction<decimal> dAction = new PerformAction<decimal>(21.55m); dAction.IdentifyDataType(); PerformAction<string> sAction = new PerformAction<string>("Hello Generics"); sAction.IdentifyDataType(); Console.ReadLine();
We have used the exact same class but let it perform with three very different data types. This kind of flexibility is a very powerful feature in your code.
Another feature of C# is that you can constrain the generic types implemented:
IDisposable
interface can be used with the generic class. Change your generic class by adding where T : IDisposable
to it. Your generic class should now look like this:public class PerformAction<T> where T : IDisposable { private T _value; public PerformAction(T value) { _value = value; } public void IdentifyDataType() { WriteLine($"The data type of the supplied variable is {_value.GetType()}"); } }
Visual Studio will tell you that the types underlined by the red squiggly lines do not implement IDisposable
and therefore can't be supplied to the PerformAction
generic class.
DataSet dsData = new DataSet(); PerformAction<DataSet> oAction = new PerformAction<DataSet>(dsData); oAction.IdentifyDataType();
DataSet
type implements IDisposable
, and therefore, it is a valid type to pass to our generic class. Go ahead and run the console application:The DataSet
type is valid, and the generic class performs as expected, identifying the type of the parameter passed to the constructor.
But what about generic methods? Well, just like generic classes, generic methods also do not specify their type at design time. It is only known when the method is called. Let's have a look at the following implementation of generic methods:
MyHelperClass
:public class MyHelperClass { }
InspectType
. What is interesting about this generic method is that it can return multiple types because the return type is also marked with the generic type parameter. Your generic method does not have to return anything. It can also be declared as void
:public class MyHelperClass { public T InspectType<T>(T value) { } }
(T)
when returning it:public class MyHelperClass { public T InspectType<T>(T value) { WriteLine($"The data type of the supplied parameter is {value.GetType()}"); return (T)value; } }
MyEnum
. The generic method can also accept enumerators:public enum MyEnum { Value1, Value2, Value3 }
oHelper
class and passing different values to it:MyHelperClass oHelper = new MyHelperClass(); var intExample = oHelper.InspectType(25); Console.WriteLine($"An example of this type is {intExample}"); var decExample = oHelper.InspectType(11.78m); Console.WriteLine($"An example of this type is {decExample}"); var strExample = oHelper.InspectType("Hello Generics"); Console.WriteLine($"An example of this type is {strExample}"); var enmExample = oHelper.InspectType(MyEnum.Value2); Console.WriteLine($"An example of this type is {enmExample}"); Console.ReadLine();
Generic methods can be used in a multitude of situations. This is however only an introduction to generic classes and methods. It is recommended that you do further research to learn how to implement generics in your code appropriately.
At the heart of generics lies the ability to reuse a single class or method. It allows developers to essentially not repeat similar code throughout their code base. This conforms well to the Don't Repeat Yourself (DRY) principle. This design principle states that a specific bit of logic should be represented in code only once.
Using generic classes also allows developers to create a class that is type safe when compiling. Type safe basically means that the developer can be assured of the type of the object and can use the class in a specific way without experiencing any unexpected behavior. Therefore, the compiler takes over the burden of type safety.
Generics also allow developers to write less code because code can be reused, and less code also performs better.
18.190.217.253