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:

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


Generic Methods

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

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


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:

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

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

Struct

Default constructor using new()

BaseClass

Interface


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

1 个月

Very helpful

要查看或添加评论,请登录

Asp.net with c#的更多文章

社区洞察

其他会员也浏览了