Generics

Back

Loading concept...

C# Generics: The Magic Shape-Shifter Box 🎁

Imagine you have a magical box. This box can hold anything you want—toys, cookies, or even dinosaurs! But here’s the cool part: once you decide what goes inside, it only accepts that thing. No mix-ups. No surprises.

That’s what Generics are in C#. They let you create code that works with any type while keeping everything safe and organized.


🌟 Generic Fundamentals

What Problem Do Generics Solve?

Without Generics (The Messy Way):

Think of a toy box that accepts “anything.” You throw in a car, a doll, and suddenly… a banana? Now when you reach in, you don’t know what you’ll get!

ArrayList box = new ArrayList();
box.Add(5);        // number
box.Add("hello");  // text
// Confusing! What's inside?

With Generics (The Smart Way):

Now imagine a box with a label. It says “TOYS ONLY.” Everyone knows what goes in, and you always know what comes out!

List<int> numberBox = new List<int>();
numberBox.Add(5);
numberBox.Add(10);
// Only numbers allowed!

The Magic <T> Symbol

The letter T is like a placeholder—a blank space waiting for you to fill in.

T = "Whatever type YOU choose"

When you write <T>, you’re saying: “I’ll tell you the type later!”

Real Example:

// T becomes int
List<int> ages = new List<int>();

// T becomes string
List<string> names = new List<string>();

Why Use Generics?

Without Generics With Generics
Type errors at runtime 💥 Errors caught early ✅
Need type casting No casting needed
Slower (boxing/unboxing) Faster performance
Less readable code Crystal clear code

🏠 Generic Classes

Building Your Own Magic Box

A generic class is like building your own customizable container. You design it once, then use it with any type!

The Blueprint:

public class Box<T>
{
    private T item;

    public void Put(T thing)
    {
        item = thing;
    }

    public T Get()
    {
        return item;
    }
}

Using Your Box:

// A box for toys (strings)
Box<string> toyBox = new Box<string>();
toyBox.Put("Teddy Bear");
string myToy = toyBox.Get();

// A box for numbers
Box<int> scoreBox = new Box<int>();
scoreBox.Put(100);
int myScore = scoreBox.Get();

Multiple Type Parameters

What if your magic box needs TWO labels? No problem! Use multiple letters.

public class Pair<TFirst, TSecond>
{
    public TFirst First { get; set; }
    public TSecond Second { get; set; }
}

Usage:

// A pair of name (string) and age (int)
Pair<string, int> person =
    new Pair<string, int>();
person.First = "Luna";
person.Second = 8;
graph TD A["Pair&amp;lt;TFirst, TSecond&amp;gt;"] B["TFirst = string"] C["TSecond = int"] D["Result: Pair&amp;lt;string, int&amp;gt;"] A --> B A --> C B --> D C --> D

🔧 Generic Methods

A Method That Transforms

Instead of making a whole class generic, you can make just one method generic. It’s like having a single magic wand that works on anything!

The Swap Trick:

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

Watch It Work:

int x = 5, y = 10;
Swap(ref x, ref y);
// Now x=10, y=5!

string first = "Hello";
string second = "World";
Swap(ref first, ref second);
// Now first="World", second="Hello"!

Generic Methods in Non-Generic Classes

You can add a generic method to a regular class. No need to make the whole class generic!

public class Helper
{
    public T GetFirst<T>(T[] items)
    {
        return items[0];
    }
}

Usage:

Helper helper = new Helper();

int[] numbers = {1, 2, 3};
int first = helper.GetFirst(numbers);
// first = 1

string[] words = {"Hi", "Bye"};
string firstWord = helper.GetFirst(words);
// firstWord = "Hi"

🔒 Generic Constraints

Setting the Rules

Sometimes your magic box shouldn’t accept everything. What if you want only toys with batteries? You need constraints!

Constraints say: “T must be a certain kind of thing.”

The Constraint Keywords

Constraint Meaning
where T : class T must be a reference type
where T : struct T must be a value type
where T : new() T must have a constructor
where T : SomeClass T must inherit from SomeClass
where T : ISomething T must implement interface

Examples in Action

Only Classes Allowed:

public class Holder<T> where T : class
{
    public T Item { get; set; }
}

// Works!
Holder<string> h1 = new Holder<string>();

// Error! int is not a class
// Holder<int> h2 = new Holder<int>();

Must Have Empty Constructor:

public T CreateNew<T>() where T : new()
{
    return new T();
}

Must Implement Interface:

public void Print<T>(T item)
    where T : IPrintable
{
    item.Print();
}

Combining Multiple Constraints

You can stack rules together!

public class Manager<T>
    where T : class, IComparable, new()
{
    // T must be:
    // 1. A reference type (class)
    // 2. Implement IComparable
    // 3. Have an empty constructor
}
graph TD A["T enters the gate"] B{Is it a class?} C{Has IComparable?} D{Has new constructor?} E["✅ Allowed In!"] F["❌ Rejected!"] A --> B B -->|Yes| C B -->|No| F C -->|Yes| D C -->|No| F D -->|Yes| E D -->|No| F

🔄 Covariance and Contravariance

The Direction of Types

This sounds fancy, but it’s really about which direction types can flow. Think of it like water flowing uphill or downhill.

Covariance (OUT = Can Go Up)

Covariance lets you use a more specific type where a general type is expected.

Imagine: A basket of Red Apples 🍎 can be treated as a basket of Fruits 🍇🍊🍎.

The Keyword: out

// IEnumerable is covariant
IEnumerable<string> strings =
    new List<string> { "Hi" };

// string inherits from object
// So this works!
IEnumerable<object> objects = strings;

Creating Covariant Interface:

public interface IProducer<out T>
{
    T Produce();  // T only goes OUT
}

Contravariance (IN = Can Go Down)

Contravariance is the opposite. You can use a more general type where a specific type is expected.

Imagine: Someone who can eat ANY Fruit 🍇🍊🍎 can definitely eat Red Apples 🍎.

The Keyword: in

// Action is contravariant
Action<object> printObject =
    (o) => Console.WriteLine(o);

// object is more general than string
// So this works!
Action<string> printString = printObject;

Creating Contravariant Interface:

public interface IConsumer<in T>
{
    void Consume(T item);  // T only goes IN
}

The Simple Rule

Direction Keyword Memory Trick
Covariance out Data goes OUT (return values)
Contravariance in Data goes IN (parameters)
graph TD subgraph Covariance A["Child Type"] -->|Can become| B["Parent Type"] C["List&amp;lt;Cat&amp;gt;"] -->|Can become| D["IEnumerable&amp;lt;Animal&amp;gt;"] end subgraph Contravariance E["Parent Type"] -->|Can become| F["Child Type"] G["Action&amp;lt;Animal&amp;gt;"] -->|Can become| H["Action&amp;lt;Cat&amp;gt;"] end

Quick Summary Table

Concept Keyword Direction Use When
Covariance out Child → Parent Returning values
Contravariance in Parent → Child Taking parameters
Invariant none No conversion Both in and out

🎯 Putting It All Together

Here’s a complete example combining everything:

// Generic class with constraint
public class Zoo<T> where T : Animal
{
    private List<T> animals =
        new List<T>();

    // Generic method
    public void Add<TAnimal>(TAnimal pet)
        where TAnimal : T
    {
        animals.Add(pet);
    }

    // Covariant return
    public IEnumerable<T> GetAll()
    {
        return animals;
    }
}

// Usage
Zoo<Mammal> mammalZoo = new Zoo<Mammal>();
mammalZoo.Add(new Cat());
mammalZoo.Add(new Dog());

// Covariance in action!
IEnumerable<Animal> allAnimals =
    mammalZoo.GetAll();

💡 Key Takeaways

  1. Generics = Type-Safe Templates Write once, use with any type safely

  2. <T> is Your Placeholder Fill it in when you use the class/method

  3. Constraints = Safety Rules Control what types are allowed

  4. Covariance (out) = Child → Parent For when you’re giving data OUT

  5. Contravariance (in) = Parent → Child For when you’re taking data IN


🎉 You Did It!

Generics might seem tricky at first, but now you know they’re just smart, flexible boxes. You control what goes in, and C# makes sure nothing unexpected sneaks out!

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.