Understanding Generics in C#

Understanding Generics in C#

When I started my coding journey in C# and learned to create projects, I wondered if we could define classes and methods without specifying exact data types.

I got this answer when I came across Generics. This powerful feature in C# allows devs exactly what I was wondering.

Web developers must always try to reusable code instead of repeating the same code multiple times — saves time, and code becomes efficient and maintainable.

What are Generics?

So technically, generics is like a placeholder where you can define your classes, methods, or even interfaces without worrying about the type of the code until it is instantiated or invoked.

What’s best thing is, you can work with different data types without duplicating your code.

For example, consider a non-generic ArrayList:

ArrayList list = new ArrayList();
list.Add(1);
list.Add("hello");
list.Add(3.14);        

What happens here is that ArrayList will allow you to store any data type — which is flexible, but what you need to understand here is that it is prone to runtime errors.

Do you know what will happen if you try to retrieve a value of the wrong type? — It will result in an exception. And this is an unwanted situation that any dev wants to tackle.

In such cases, we can take the help of generics. It eliminates this problem by ensuring type safety at compile time.

List<int> list = new List<int>();
list.Add(1);
// list.Add("hello"); // This will cause a compile-time error.        

So as you can see, I have used int type and if I try to add a string type into this list, I would receive a compile-time error.

Let’s look at some advantages generics has to offer:

  1. Generics ensures that any data type mismatch is resolved at compile time itself, rather than resolving at runtime.
  2. Generics avoid boxing and unboxing which reduces overhead and improves performance.
  3. It reduces code duplication.
  4. A single generic class can support multiple data types (which we will see in a bit).


Generic Classes

A generic class allows you to define a class with type params.

Here’s how it can be defined:

Article content

The T here acts as a placeholder and makes the class reusable for any data type. Isn’t that great?!

The _value is a private field of type T. It will hold the value provided during the object’s creation.

Finally in the Main(), I have used two data types but reused the same GetValue(). It creates an instance of GenericContainer and uses multiple data types to demonstrate code reusability.

Output

Article content

Generic Methods

Methods with type parameters are generic methods. The parameters are specified when the method is called.

Article content

Here I have taken a classic swap example that demonstrates the usage of generic methods.

The Swap<T>` indicates a generic method and can operate on any data type.

ref T a and ref T b — both parameters are passed by reference (ref keyword). This means the method modifies the original variables rather than working with copies.

Output

Article content

Built-in Generic Types

Did you know .NET Framework already provides several generic types and classes?

  • Collections — List<T>, Dictionary<TKey, TValue> , Queue<T> , Stack<T>
  • Nullable Types — Nullable<T>
  • Delegates and Events — Func<T>, Action<T>, and Predicate<T>


Generic Interfaces

Just like normal interfaces, a generic interface also defines a contract for classes. Any class that inherits this interface must use methods declared in it.

Here’s what it would look like:

Article content

Here, IRepository<T> is an interface that mandates all the classes that inherit this interface to implement all the methods declared in it.

In other words, this interface defines a contract for classes that inherit this.

The data type is flexible because of its <T> type.

Output

Article content

Constraints in Generics

C# allows you to use constraints that allow restriction to specify certain types while instantiating generic types.

Using constraints, when we create a new instance of a generic type we can restrict what types we want to substitute for type parameters.

If we try to substitute a type that does not comply with the constraint we can expect a compile-time error.

We specify constraints using the where clause. No, don’t worry. This is not SQL class 😉

Pay attention to the description given.

  1. where T : class — T must be a reference type.
  2. where T : struct — T must be a value type.
  3. where T : new() — T must have a parameterless constructor.
  4. where T : BaseClass — T must inherit from BaseClass.
  5. where T : interface — T must implement the specified interface.

A simple example of each that would help you understand how it is declared

Class

Article content

Struct

Article content

Default constructor using new()

Article content

BaseClass

Article content

Interface

Article content

Limitations

Despite the generic’s flexibility, it does have some limitations.

  • This code:

List<object> list = new List<string>();        

Is still invalid even though the string is derived from an object — strange, but true.

  • You cannot enforce operators like +, -, or == in generics.

public T Add<T>(T a, T b) where T : /* What to write here? */
{
    return a + b; // This throws error because '+' operator is not defined for T.
}        

Ending Note

Make use of such features that C# provides to enhance your code and write efficient pieces of code. By understanding generics, you can reduce code duplication and improve app performance.

Despite its limitations, devs must use generics depending on their project requirement.

Thank you for reading! 😊


Abdulfetah Siraj

Application Support Specialist at Tata Consultancy Services

3mo

Very helpful

To view or add a comment, sign in

More articles by Asp.net with c#

Insights from the community

Others also viewed

Explore topics