C# - Collections and Generics

C# Collections and Generics - Complete Tutorial with Examples

C# Collections and Generics

C# provides powerful and flexible data structures known as Collections to store and manage groups of related objects. Additionally, with the introduction of Generics in .NET 2.0, type safety and performance in collection handling have improved significantly. This tutorial explores the most commonly used collection types in C# including List, Dictionary, HashSet, Stack, Queue, and explains how to work with Generics, create generic classes and methods, and use constraints.

What are Collections in C#?

Collections are classes provided in the System.Collections and System.Collections.Generic namespaces that allow you to store multiple items. Unlike arrays, collections are dynamic in nature, meaning they can grow or shrink as needed.

Types of Collections in C#

  • Non-generic Collections (e.g., ArrayList, Hashtable)
  • Generic Collections (e.g., List<T>, Dictionary<TKey,TValue>)
  • Concurrent Collections (for thread-safe operations)

Why Use Generic Collections?

  • Type Safety
  • Better Performance
  • Compile-time Checking
  • No Boxing/Unboxing for value types

Commonly Used Generic Collections in C#

1. List<T> - Dynamic Array

List is a generic replacement for the ArrayList class. It stores elements of a specific type and supports methods such as Add, Remove, Contains, etc.

using System.Collections.Generic;

List<string> names = new List<string>();
names.Add("John");
names.Add("Alice");
names.Add("Bob");

foreach (string name in names)
{
    Console.WriteLine(name);
}

2. Dictionary<TKey, TValue> - Key/Value Pair

Dictionary stores data as key/value pairs, where keys must be unique.

Dictionary<int, string> users = new Dictionary<int, string>();
users.Add(1, "Alice");
users.Add(2, "Bob");

Console.WriteLine(users[1]); // Output: Alice

3. HashSet<T> - Unique Collection

HashSet stores unique elements and ignores duplicates automatically.

HashSet<int> numbers = new HashSet<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Add(10); // Duplicate, will not be added

foreach (int num in numbers)
{
    Console.WriteLine(num);
}

4. Stack<T> - LIFO Collection

Stack follows Last-In-First-Out. You use Push and Pop methods to manage data.

Stack<string> history = new Stack<string>();
history.Push("Page1");
history.Push("Page2");

Console.WriteLine(history.Pop()); // Output: Page2

5. Queue<T> - FIFO Collection

Queue follows First-In-First-Out. You use Enqueue and Dequeue methods.

Queue<string> queue = new Queue<string>();
queue.Enqueue("Customer1");
queue.Enqueue("Customer2");

Console.WriteLine(queue.Dequeue()); // Output: Customer1

Working with Collections: Additional Methods

List<T> Methods

  • Add()
  • Insert()
  • Remove()
  • Clear()
  • Contains()
  • IndexOf()
List<int> scores = new List<int> { 90, 85, 75 };
scores.Insert(1, 88);   // Insert 88 at index 1
scores.Remove(75);      // Remove value 75
bool exists = scores.Contains(90);

What are Generics in C#?

Generics allow you to define a class, method, or interface with a placeholder for the data type. It enhances reusability and type safety without sacrificing performance.

Generic Class Example

public class Box<T>
{
    public T Value;

    public void Display()
    {
        Console.WriteLine("Value: " + Value);
    }
}
Box<int> intBox = new Box<int>();
intBox.Value = 123;
intBox.Display();

Box<string> strBox = new Box<string>();
strBox.Value = "Hello Generics";
strBox.Display();

Generic Method Example

public void Swap<T>(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}
int x = 5, y = 10;
Swap<int>(ref x, ref y);
Console.WriteLine($"x = {x}, y = {y}");

Generic Constraints in C#

Constraints restrict the types that can be used as arguments for a generic class or method.

Types of Constraints

  • where T : struct – Value type
  • where T : class – Reference type
  • where T : new() – Must have a parameterless constructor
  • where T : BaseClass – Must derive from BaseClass
  • where T : Interface – Must implement an interface
public class Repository<T> where T : class, new()
{
    public T Create()
    {
        return new T();
    }
}

Generic Interfaces

Generic interfaces provide a blueprint for reusable components that work with any data type.

public interface IContainer<T>
{
    void Add(T item);
    T Get();
}

Built-in Generic Interfaces

  • IEnumerable<T>
  • IList<T>
  • IDictionary<TKey, TValue>
List<string> cities = new List<string> { "Delhi", "Chennai", "Kolkata" };

IEnumerable<string> query = from c in cities
                            where c.StartsWith("C")
                            select c;

foreach (var city in query)
{
    Console.WriteLine(city);
}

Advantages of Using Generics

  • Improved code reusability
  • Type safety at compile time
  • Better performance (avoids boxing/unboxing)
  • Reduces code duplication

Real-World Use Case

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Catalog<T> where T : class
{
    private List<T> items = new List<T>();

    public void AddItem(T item)
    {
        items.Add(item);
    }

    public List<T> GetAll()
    {
        return items;
    }
}
Catalog<Product> productCatalog = new Catalog<Product>();
productCatalog.AddItem(new Product { Id = 1, Name = "Laptop" });
productCatalog.AddItem(new Product { Id = 2, Name = "Mobile" });

foreach (var item in productCatalog.GetAll())
{
    Console.WriteLine($"{item.Id}: {item.Name}");
}

When to Use Generics vs Non-Generics

FeatureGenericsNon-Generics
Type SafetyYesNo
PerformanceHighLow (Boxing/Unboxing)
FlexibilityHighLimited
Compile-Time CheckingYesNo

C# Collections and Generics form the backbone of many data processing applications. They offer flexibility, performance, and type safety, which are critical for robust software development. Understanding and applying the right collection type and using generics effectively can make your code more readable, efficient, and maintainable.

Beginner 5 Hours
C# Collections and Generics - Complete Tutorial with Examples

C# Collections and Generics

C# provides powerful and flexible data structures known as Collections to store and manage groups of related objects. Additionally, with the introduction of Generics in .NET 2.0, type safety and performance in collection handling have improved significantly. This tutorial explores the most commonly used collection types in C# including List, Dictionary, HashSet, Stack, Queue, and explains how to work with Generics, create generic classes and methods, and use constraints.

What are Collections in C#?

Collections are classes provided in the System.Collections and System.Collections.Generic namespaces that allow you to store multiple items. Unlike arrays, collections are dynamic in nature, meaning they can grow or shrink as needed.

Types of Collections in C#

  • Non-generic Collections (e.g., ArrayList, Hashtable)
  • Generic Collections (e.g., List<T>, Dictionary<TKey,TValue>)
  • Concurrent Collections (for thread-safe operations)

Why Use Generic Collections?

  • Type Safety
  • Better Performance
  • Compile-time Checking
  • No Boxing/Unboxing for value types

Commonly Used Generic Collections in C#

1. List<T> - Dynamic Array

List is a generic replacement for the ArrayList class. It stores elements of a specific type and supports methods such as Add, Remove, Contains, etc.

using System.Collections.Generic; List<string> names = new List<string>(); names.Add("John"); names.Add("Alice"); names.Add("Bob"); foreach (string name in names) { Console.WriteLine(name); }

2. Dictionary<TKey, TValue> - Key/Value Pair

Dictionary stores data as key/value pairs, where keys must be unique.

Dictionary<int, string> users = new Dictionary<int, string>(); users.Add(1, "Alice"); users.Add(2, "Bob"); Console.WriteLine(users[1]); // Output: Alice

3. HashSet<T> - Unique Collection

HashSet stores unique elements and ignores duplicates automatically.

HashSet<int> numbers = new HashSet<int>(); numbers.Add(10); numbers.Add(20); numbers.Add(10); // Duplicate, will not be added foreach (int num in numbers) { Console.WriteLine(num); }

4. Stack<T> - LIFO Collection

Stack follows Last-In-First-Out. You use Push and Pop methods to manage data.

Stack<string> history = new Stack<string>(); history.Push("Page1"); history.Push("Page2"); Console.WriteLine(history.Pop()); // Output: Page2

5. Queue<T> - FIFO Collection

Queue follows First-In-First-Out. You use Enqueue and Dequeue methods.

Queue<string> queue = new Queue<string>(); queue.Enqueue("Customer1"); queue.Enqueue("Customer2"); Console.WriteLine(queue.Dequeue()); // Output: Customer1

Working with Collections: Additional Methods

List<T> Methods

  • Add()
  • Insert()
  • Remove()
  • Clear()
  • Contains()
  • IndexOf()
List<int> scores = new List<int> { 90, 85, 75 }; scores.Insert(1, 88); // Insert 88 at index 1 scores.Remove(75); // Remove value 75 bool exists = scores.Contains(90);

What are Generics in C#?

Generics allow you to define a class, method, or interface with a placeholder for the data type. It enhances reusability and type safety without sacrificing performance.

Generic Class Example

public class Box<T> { public T Value; public void Display() { Console.WriteLine("Value: " + Value); } }
Box<int> intBox = new Box<int>(); intBox.Value = 123; intBox.Display(); Box<string> strBox = new Box<string>(); strBox.Value = "Hello Generics"; strBox.Display();

Generic Method Example

public void Swap<T>(ref T a, ref T b) { T temp = a; a = b; b = temp; }
int x = 5, y = 10; Swap<int>(ref x, ref y); Console.WriteLine($"x = {x}, y = {y}");

Generic Constraints in C#

Constraints restrict the types that can be used as arguments for a generic class or method.

Types of Constraints

  • where T : struct – Value type
  • where T : class – Reference type
  • where T : new() – Must have a parameterless constructor
  • where T : BaseClass – Must derive from BaseClass
  • where T : Interface – Must implement an interface
public class Repository<T> where T : class, new() { public T Create() { return new T(); } }

Generic Interfaces

Generic interfaces provide a blueprint for reusable components that work with any data type.

public interface IContainer<T> { void Add(T item); T Get(); }

Built-in Generic Interfaces

  • IEnumerable<T>
  • IList<T>
  • IDictionary<TKey, TValue>
List<string> cities = new List<string> { "Delhi", "Chennai", "Kolkata" }; IEnumerable<string> query = from c in cities where c.StartsWith("C") select c; foreach (var city in query) { Console.WriteLine(city); }

Advantages of Using Generics

  • Improved code reusability
  • Type safety at compile time
  • Better performance (avoids boxing/unboxing)
  • Reduces code duplication

Real-World Use Case

public class Product { public int Id { get; set; } public string Name { get; set; } } public class Catalog<T> where T : class { private List<T> items = new List<T>(); public void AddItem(T item) { items.Add(item); } public List<T> GetAll() { return items; } }
Catalog<Product> productCatalog = new Catalog<Product>(); productCatalog.AddItem(new Product { Id = 1, Name = "Laptop" }); productCatalog.AddItem(new Product { Id = 2, Name = "Mobile" }); foreach (var item in productCatalog.GetAll()) { Console.WriteLine($"{item.Id}: {item.Name}"); }

When to Use Generics vs Non-Generics

FeatureGenericsNon-Generics
Type SafetyYesNo
PerformanceHighLow (Boxing/Unboxing)
FlexibilityHighLimited
Compile-Time CheckingYesNo

C# Collections and Generics form the backbone of many data processing applications. They offer flexibility, performance, and type safety, which are critical for robust software development. Understanding and applying the right collection type and using generics effectively can make your code more readable, efficient, and maintainable.

Related Tutorials

Frequently Asked Questions for General

line

Copyrights © 2024 letsupdateskills All rights reserved