Generics is the concept of generalising types of objects in their class and method. It can be thought of as a variable for type. The types of generics are generalised until a class or method is declared. A generic class can be used to create a code pattern that can support multiple object types.
Creating Generics
Generics can be created by adding <T>
after the class name. Here, T
is the generic type that gets set while calling it. A class can have multiple generic types that can be added by separating them with commas as <T,U,V>
. The general naming convention for creating generic types starts from T and moves alphabetically as T, U, V, etc.
This class has the GetMyType
method that simply returns the type of generic class. A generic class is not very useful as the type T
can be anything. To use T
, we can limit its type.
Constraint on Generic Type
A constraint limits the generic type, which allows us to use more properties of generic types. A constraint can be given to a generic type by using the where
keyword.
Let's create a constraint for T
in MyGenericClass
. We will create its constraint as Monobehaviour
. This limits the value of T
as MonoBehaviour
or it's child class. This allows the use of MonoBehaviour
's properties.
Using Generics with Inheritance
We will be creating an inventory system to handle consumable and non-consumable items for our demonstration.
Let us first create a base class Item
. It will contain properties itemName
, itemPrice
, and ownedQuantity
. This will contain two public methods, GetOwnedQuantity
and PurchaseItem
. A virtual method Use
will be used to consume the item. This method will be handled separately from the child classes.
Let's create two child classes deriving from the Item
base class. Both will have different constructors. Also, they both will override the Use
method separately. The ConsumableItem
class will decrease owned items by 1 when used.
To further differentiate the two classes, let's also add UnEquip
method to NonConsumableItem
class.
Now that we have the required base classes, we will be creating a controller class for them. We will use a generic system to make a controller.
Let's create a class ItemClass
that has the generic type T
. The type T
will be limited to Item
class so that we can use the properties of the Item
class. In this class, create a list of type T
so that items can be stored.
Note that List
is also a generic class, so we can pass T
as a type. Now, we create the methods AddItem
to add inventory items, GetItem
to get items from the name and UseItem
to use items with the name.
Now to demonstrate its use, let's create a Demo
class with CreateInventory
method. This method will create a controller, add items, and use the item.
At first, create two controllers consumableItemController
and nonConsumableItemController
for handling ConsumableItem
and NonConsumable
item by passing them as a generic type. Now, add items to them using the AddItem
method. Note that only items of defined generic type while creating a controller can be added.
Added items can be used by calling the ItemController.Use(string)
method. Also, try GetItem
using the ItemCOntroller.GetItem(string)
method. This will always return the defined generic type for the controller.
Characteristics of a Generic System
- A generic class can be used as a template for code. It can make a block of code reusable. In the above
ItemController
class,AddItem
,GetItem
andUseItem
methods were reused for bothConsumable
andNonConsumable
classes. This generalisation makes modification easier as only the generic class needs to be modified. However, this should be used in moderation, as using it too much will make it harder to maintain. - Generic classes are type-safe. Once a generic class is defined with a type, its type cannot be changed. This can be seen in the example above when we created the
consumableItemController
andNonConsumableItemController
object.consumableItemController
is created usingConsumableItem
as the generic type, so it is expecting an object of the same type when callingAddItem
and returning an object of the same type when callingGetItem
. - Type check is done at compile time. This lets us find issues when compiling rather than finding issues during runtime. Fixing issues during compiling is easier than during the runtime.
- Strong type checking is used in a generic system. Once an object is created with a type, even its parent class cannot be used instead of the object.
Let us consider a snippet from the above example,
consumableItemsController.AddItem(new ConsumableItem("Apple",50,6));
Here,consumableItemsController
was created usingConsumeableItem
soAddItem
only accepts an object of typeConsumableItem
.
The following line gives the compile-time error,
consumableItemsController.AddItem(new Item());
This is because strong type checking is done.Item
is not accepted even if it is the base class forConsumableItem
. This eliminates casting as we will always receive items of provided generic type. - Generics are more efficient as it removes the need for boxing, unboxing and casting objects.
Using generic systems, re-usable code can be written that can be easily maintained. I hope this blog helps you implement a generic system in your system.
Thank you for reading!