Thursday, October 9, 2025

🧠 What is Generics in C#? Complete Guide with Examples

 📘 Introduction

In C#, Generics are one of the most powerful features introduced with the .NET Framework 2.0.
They allow you to define classes, methods, interfaces, and collections that can work with any data type — while still maintaining type safety and performance.

In simple words, Generics help you write code once and use it with any data type.


⚙️ What Are Generics?

Generics let you define a placeholder type (called a type parameter) that will be replaced with a specific type when you create an instance of a class or call a method.

This means you can write:

List<int> List<string> List<Employee>

—all using the same generic List<T> class.


💡 Example Without Generics

public class Calculator { public int Add(int a, int b) { return a + b; } }

If you want to add float or double values, you’ll have to write multiple methods for each data type.
This leads to code duplication and poor reusability.


⚙️ Example With Generics

public class Calculator<T> { public T Add(T a, T b) { dynamic x = a; dynamic y = b; return x + y; } } class Program { static void Main() { Calculator<int> intCalc = new Calculator<int>(); Console.WriteLine("Sum (int): " + intCalc.Add(10, 20)); Calculator<double> doubleCalc = new Calculator<double>(); Console.WriteLine("Sum (double): " + doubleCalc.Add(10.5, 20.7)); } }

Output

Sum (int): 30 Sum (double): 31.2

Here, T is a type parameter. It allows the Calculator class to operate with any data type (int, double, string, etc.).


🧱 Generic Method Example

You can also create generic methods inside normal classes.

public class DisplayData { public void ShowData<T>(T data) { Console.WriteLine("Value: " + data); } } class Program { static void Main() { DisplayData obj = new DisplayData(); obj.ShowData<int>(101); obj.ShowData<string>("Hello Generics"); } }

Output

Value: 101 Value: Hello Generics

Here, the method ShowData<T>() can accept any data type without overloading.


📚 Generic Collections in C#

C# provides several built-in generic collections inside the System.Collections.Generic namespace.
These are type-safe and more efficient than old non-generic collections like ArrayList or Hashtable.

Generic CollectionDescription
List<T>Dynamic array of type T
Dictionary<TKey, TValue>Key-value pair collection
Queue<T>FIFO (First-In-First-Out) collection
Stack<T>LIFO (Last-In-First-Out) collection

Example:

List<string> names = new List<string>(); names.Add("Hasitha"); names.Add("Cherry"); foreach (var name in names) { Console.WriteLine(name); }

Output

Hasitha Cherry

🧩 Benefits of Generics in C#

BenefitDescription
Type SafetyPrevents type mismatches at compile time
PerformanceAvoids boxing/unboxing for value types
ReusabilityWrite code once, use with any type
MaintainabilityCleaner and less repetitive code

🔄 Generic Constraints (Optional but Powerful)

Sometimes, you might want to restrict which data types can be used with your generic class.

Example:

public class DataManager<T> where T : class { public void Display(T data) { Console.WriteLine("Data: " + data); } }

Here, where T : class means only reference types can be used.


🚀 Conclusion

Generics in C# make your code reusable, efficient, and type-safe.
They help developers build scalable and maintainable applications, especially in large projects using .NET Core or ASP.NET.

🧩 Understanding Adapter Design Pattern in Software Development


🔍 Introduction

In modern software architecture, systems often need to integrate with third-party libraries, legacy code, or APIs that don’t match our application's interface. The Adapter Design Pattern is a structural design pattern that bridges this gap by converting one interface into another that the client expects.

Simply put, it acts as a translator between incompatible interfaces — allowing two systems to work together smoothly without modifying their existing code.


💡 Real-Time Example: Power Plug Adapter

Imagine you travel from India (which uses a 3-pin plug) to the US (which uses a 2-pin socket). You can’t directly plug your Indian charger into the US socket — you need an adapter.

Similarly, in software:

  • Client → Your laptop charger (expects 3-pin socket).

  • Service / API → The US socket (2-pin).

  • Adapter → A converter that connects both.

This is exactly what the Adapter Pattern does in programming — it adapts one interface to another.


🧠 Technical Explanation

The Adapter Design Pattern involves three main components:

  1. Target Interface – The interface expected by the client.

  2. Adaptee – The existing class with a different interface.

  3. Adapter – A class that bridges the Adaptee with the Target interface.


💻 Example in C#

// Target Interface public interface INewPaymentGateway { void MakePayment(string account, double amount); } // Adaptee (Old system) public class OldPaymentSystem { public void ProcessTransaction(string customerCode, double value) { Console.WriteLine($"Payment of {value} processed for {customerCode} in Old System."); } } // Adapter Class public class PaymentAdapter : INewPaymentGateway { private readonly OldPaymentSystem _oldPaymentSystem; public PaymentAdapter(OldPaymentSystem oldPaymentSystem) { _oldPaymentSystem = oldPaymentSystem; } public void MakePayment(string account, double amount) { // Translate the request _oldPaymentSystem.ProcessTransaction(account, amount); } } // Client class Program { static void Main() { INewPaymentGateway payment = new PaymentAdapter(new OldPaymentSystem()); payment.MakePayment("USER123", 2500); } }

Output:

Payment of 2500 processed for USER123 in Old System.

✅ The PaymentAdapter bridges the old payment system (OldPaymentSystem) with the new interface (INewPaymentGateway).


🚀 Importance of Adapter Design Pattern

  • Integration Friendly: Easily integrate legacy systems with new applications.

  • Loose Coupling: Promotes flexibility by decoupling client and adaptee.

  • Reusability: Reuse existing incompatible code without rewriting it.

  • Scalability: Makes systems scalable for future interface changes.


🧩 Real-Time Use Cases

  • Connecting different payment gateways (PayPal, Stripe, Razorpay).

  • Integrating legacy APIs in new microservices.

  • Adapting different database connectors or logging frameworks.

  • In cloud or Azure services, when bridging old APIs to new versions.


🏁 Conclusion

The Adapter Design Pattern is a vital bridge for compatibility in software integration. It allows modern systems to interact with legacy code, third-party APIs, and incompatible interfaces — without rewriting the entire system. This ensures flexibility, maintainability, and reusability, key principles of robust software architecture.

Don't Copy

Protected by Copyscape Online Plagiarism Checker

Pages