*
Previous Serialization and deserialization in C# Reflection in C# Next

🔧 Generics in C#

🔧 Generics in C#

Generics in C# allow you to design classes, methods, and interfaces that are reusable and type-safe without compromising performance. They use type parameters (placeholders for actual data types) that are replaced with concrete types during usage. This approach improves code reusability, safety, and efficiency compared to using the object type.

The Problem Generics Solve

Before generics, developers often used the object type to store any data type. This caused two main issues:

  • Type Safety: The compiler couldn’t ensure all objects were of the same type. Mixing int and string values in a collection was possible, leading to runtime errors.
  • Performance: Value types (like int or float) had to be boxed and unboxed when stored as objects, adding memory and CPU overhead.

How Generics Work

Generics introduce type parameters (like T), which act as placeholders for real data types. The compiler ensures type safety and generates optimized code for each data type, eliminating boxing/unboxing overhead.

1. Generic Classes

A generic class is defined with one or more type parameters enclosed in angle brackets (<>) after the class name. The most common placeholder is T.

Example: A simple Box class that can hold any type of item.

// Generic class definition with a type parameter 'T'
public class Box<T>
{
    private T _content;

    public Box(T content)
    {
        _content = content;
    }

    public T GetContent()
    {
        return _content;
    }
}

// Usage in the Main method
Box<int> integerBox = new Box<int>(123);
Box<string> stringBox = new Box<string>("Hello, Generics!");

int myInt = integerBox.GetContent(); // No casting needed
string myString = stringBox.GetContent(); // No casting needed

Use code with caution.

2. Generic Methods

Generic methods define their own type parameters and can work independently of whether they are inside a generic class.

Example: A generic method to swap two values of any type.

public static void Swap<T>(ref T x, ref T y)
{
    T temp = x;
    x = y;
    y = temp;
}

// Usage in the Main method
int a = 1, b = 2;
Swap<int>(ref a, ref b); // Swap two integers
Console.WriteLine($"a: {a}, b: {b}"); // Output: a: 2, b: 1

string c = "First", d = "Second";
Swap<string>(ref c, ref d); // Swap two strings
Console.WriteLine($"c: {c}, d: {d}"); // Output: c: Second, d: First

Use code with caution.

3. Constraints

Constraints restrict what types can be used as type parameters. You can enforce rules like requiring a type to be a class, a struct, or to implement an interface.

Example: A generic method requiring the type to be a class implementing IComparable<T>.

public T FindMax<T>(T[] array) where T : class, IComparable<T>
{
    T max = array[0];
    foreach (T item in array)
    {
        if (item.CompareTo(max) > 0)
        {
            max = item;
        }
    }
    return max;
}

Use code with caution.

Benefits of Using Generics

  • Type Safety: Enforces compile-time type checking, preventing invalid operations.
  • Code Reusability: Write one class or method that works with multiple data types, reducing duplication.
  • Performance: Avoids boxing/unboxing, improving speed and memory efficiency.
  • Cleaner Code: No explicit type casting needed, improving readability.
  • LINQ Integration: C#’s LINQ feature is built heavily on generics, enabling type-safe query operations on collections.

Generics allow you to define classes, methods, interfaces, and delegates with a placeholder for the data type. This enables type-safe, reusable code without sacrificing performance or flexibility.

📘 Why Use Generics?

  • Type Safety: Compile-time checking prevents runtime errors.
  • Code Reusability: Write once, use with any type.
  • Performance: Avoids boxing/unboxing for value types.
  • Maintainability: Cleaner and more readable code.

🧩 Generic Class Example

  public class Box {
      public T Value { get; set; }
  }

  var intBox = new Box { Value = 10 };
  var stringBox = new Box { Value = "Hello" };
  

🔁 Generic Method Example

  public static void Swap(ref T a, ref T b) {
      T temp = a;
      a = b;
      b = temp;
  }

  int x = 1, y = 2;
  Swap(ref x, ref y); // x = 2, y = 1
  

📦 Generic Interface Example

  public interface IRepository {
      void Add(T item);
      T Get(int id);
  }
  

🧠 Built-in Generic Types

  • List<T>, Dictionary<TKey, TValue>, Queue<T>, Stack<T>
  • Func<T>, Action<T>, Predicate<T>
  • IEnumerable<T>, IComparer<T>, IEquatable<T>

✅ Best Practices

  • Use meaningful type parameter names like T, TKey, TValue.
  • Use where constraints to restrict type parameters.
  • Prefer generics over object-based code to avoid casting.
  • Use generic collections instead of non-generic ones (e.g., List<T> over ArrayList).

📌 When to Use

  • When writing reusable libraries or components.
  • For collections that store multiple types.
  • When implementing algorithms that work on any data type.

🚫 When Not to Use

  • For simple, type-specific logic—generics may add unnecessary complexity.
  • When type constraints are too restrictive or unclear.
  • In performance-critical code where generics may introduce overhead.

⚠️ Precautions

  • Be cautious with reference vs value types—avoid boxing.
  • Don’t use generics with static members—they’re shared across all instances.
  • Use where T : class or where T : new() for constructor or nullability constraints.

🎯 Advantages

  • Efficiency: No need for casting or boxing.
  • Flexibility: Works with any type.
  • Safety: Compile-time type checking.
  • Scalability: Ideal for large, reusable codebases.

📝 Conclusion

Generics in C# are a powerful feature that promote type safety, reusability, and performance. Whether you're building collections, utilities, or APIs, generics help you write cleaner and more maintainable code.

Back to Index
Previous Serialization and deserialization in C# Reflection in C# Next
*
*