Generic structures are used for any type that conforms to a particular pattern to avoid code duplication in classes, methods, or interfaces. With generic structures, type-independent methods and classes can be written, so projects can be managed more easily with less code.
List, which we often use, is actually a Generic class. Whatever type you give to this class, you create a list of that type. If we did not use Generic structures in lists, we would have to create a list for each type such as IntList, FloatList, StringList. In this way, we prevent code duplication and simplify management.
Let’s explain this with examples.
using System;
namespace GenericTest
{
public class CustomList<T>
{
public int Count => _array.Length;
public T this[int index] => _array[index];
private T[] _array;
public CustomList()
{
_array = new T[0];
}
public void Add(T t)
{
Array.Resize(ref array, array.Length + 1);
array[^1] = item;
}
public void Remove(T t)
{
int removeIndex = 0;
for (int index = 0; index < _array.Length; index++)
{
if (t.Equals(_array[index]))
{
removeIndex = index;
}
}
for (int index = removeIndex; index < _array.Length - 1; index++)
{
_array[index] = _array[index + 1];
}
Array.Resize(ref _array, _array.Length - 1);
}
}
class Program
{
static void Main()
{
CustomList<string> names = new CustomList<string>();
names.Add("tarik");
names.Add("ali");
names.Add("veli");
names.Add("ayse");
names.Add("fatma");
LogElements(names);
names.Remove("tarik");
LogElements(names);
CustomList<int> numbers = new CustomList<int>();
numbers.Add(2);
numbers.Add(6);
LogElements(numbers);
}
private static void LogElements<T>(CustomList<T> list)
{
Console.WriteLine($"\nCount: {list.Count} =>");
for (int index = 0; index < list.Count; index++)
{
Console.WriteLine($"{index}. element: {list[index]}");
}
}
}
}
To create a generic structure, <T> syntax is written after the class name or method name. Here T is the T from Type and this usage is generally preferred. So whatever you write here will be your definition.
The basis of lists is array. In this example, we created our own Generic List. Currently, there are only Add and Remove methods in our list. With these methods, we can add and remove from the array. When we send int type to this class, our array variable becomes int, when we send string type, our array variable becomes string. Here we created a string and an int list and played around with these lists. With the Generic Method, we were able to log both of these lists. Whatever type of CustomList we give to LogElements Generic method, it is processed according to that type. Our output is as follows.
Count: 5 =>
0. element: tarik
1. element: ali
2. element: veli
3. element: ayse
4. element: fatma
Count: 4 =>
0. element: ali
1. element: veli
2. element: ayse
3. element: fatma
Count: 2 =>
0. element: 2
1. element: 6
Generic Constraints
Whatever type parameter is given to generic structures, the structure operates using that type. You can define Generic structures written for certain types, with constraints, so that only defined types can be given parameters to those structures. Now let’s create a class by constraining our Generic structure.
namespace GenericTest
{
public class AnimalGeneric<T> where T : Animal
{
public void Speak(T item)
{
Console.WriteLine("Animal Speaking...");
item.Speak();
}
}
public abstract class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Hav hav!");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Meow!");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
Cat cat = new Cat();
AnimalGeneric<Animal> animals = new AnimalGeneric<Animal>();
animals.Speak(dog);
animals.Speak(cat);
}
}
}
Here, we have constrained the type to be given parameters to our Generic class. Only types derived from the Animal class can be given as parameters. Our output is as follows.
Animal Speaking...
Hav hav!
Animal Speaking...
Meow!
Inheritance in Generic Classes
Generic classes can also extend by inheritance and override methods in the superclass. Let’s prove this with a list that inherits List from the System.Collections.Generic library.
namespace GenericTest
{
public class EventList<T> : List<T>
{
public event Action<T> Added;
public event Action<T> Removed;
public new void Add(T item)
{
base.Add(item);
Added?.Invoke(item);
}
public new void Remove(T item)
{
base.Remove(item);
Removed?.Invoke(item);
}
}
class Program
{
static void Main()
{
EventList<string> names = new EventList<string>();
names.Added += OnAdded;
names.Removed += OnRemoved;
names.Add("tarik");
names.Add("ali");
names.Add("fatma");
names.Remove("tarik");
}
private static void OnAdded(string obj)
{
Console.WriteLine($"Added item: {obj}");
}
private static void OnRemoved(string obj)
{
Console.WriteLine($"Removed item: {obj}");
}
}
}
The list we created inherits the List and stores the elements in this list. The event is triggered when an element is added or removed from the list with the Add and Remove methods. The Add and Remove methods also override the methods in the List class. The new keyword defined in the methods here is used to override non-virtual and static methods. The output is as follows.
Added item: tarik
Added item: ali
Added item: fatma
Removed item: tarik
As a result, Generic structures can avoid code duplication and simplify project management.