C# & ASP.NET Topic

C# Collections & Generics

Generics Introduction

Generics allow you to define type-safe data structures without committing to actual data types. This provides:

  • Type safety - compile-time type checking

  • Performance - avoids boxing/unboxing

  • Code reuse - write once, use with any type

Basic Generic Syntax

csharp

// Generic class
public class Container<T>
{
    public T Value { get; set; }
}

// Generic method
public T GetDefault<T>()
{
    return default(T);
}

List<T> - Dynamic Arrays

Basic List Operations

csharp

using System;
using System.Collections.Generic;

class ListExamples
{
    public static void DemonstrateList()
    {
        // Creating lists
        List<int> numbers = new List<int>();
        List<string> names = new List<string>();
        
        // Collection initializer
        List<int> primes = new List<int> { 2, 3, 5, 7, 11 };
        
        // Adding elements
        numbers.Add(10);
        numbers.Add(20);
        numbers.AddRange(new int[] { 30, 40, 50 });
        
        // Accessing elements
        Console.WriteLine($"First: {numbers[0]}"); // 10
        Console.WriteLine($"Count: {numbers.Count}"); // 6
        
        // Iterating
        foreach (int number in numbers)
        {
            Console.WriteLine(number);
        }
        
        // Checking existence
        bool hasTwenty = numbers.Contains(20); // true
        int index = numbers.IndexOf(30); // 3
    }
}

Advanced List Operations

csharp

public static void AdvancedListOperations()
{
    List<string> fruits = new List<string> 
    { 
        "apple", "banana", "orange", "grape", "kiwi" 
    };
    
    // Searching
    string found = fruits.Find(fruit => fruit.StartsWith("a")); // "apple"
    List<string> allWithA = fruits.FindAll(fruit => fruit.Contains("a"));
    
    // Sorting
    fruits.Sort(); // Alphabetical order
    fruits.Sort((x, y) => x.Length.CompareTo(y.Length)); // By length
    
    // Conversion
    string[] fruitArray = fruits.ToArray();
    List<string> upperFruits = fruits.ConvertAll(fruit => fruit.ToUpper());
    
    // Removal
    fruits.Remove("banana");
    fruits.RemoveAt(0); // Remove first element
    fruits.RemoveAll(fruit => fruit.Length > 5);
    
    // Bulk operations
    fruits.InsertRange(1, new string[] { "pear", "peach" });
    fruits.Reverse();
}

List Performance Characteristics

csharp

public class ListPerformance
{
    public static void DemonstratePerformance()
    {
        List<int> numbers = new List<int>();
        
        // Add - O(1) amortized
        for (int i = 0; i < 1000; i++)
        {
            numbers.Add(i); // Fast
        }
        
        // Insert at beginning - O(n)
        numbers.Insert(0, -1); // Slow for large lists
        
        // Access by index - O(1)
        int value = numbers[500]; // Very fast
        
        // Search - O(n)
        bool contains = numbers.Contains(999); // Linear search
        
        // Remove from end - O(1)
        numbers.RemoveAt(numbers.Count - 1); // Fast
        
        // Remove from beginning - O(n)
        numbers.RemoveAt(0); // Slow
    }
}

Dictionary<TKey, TValue> - Key-Value Pairs

Basic Dictionary Operations

csharp

public static void DemonstrateDictionary()
{
    // Creating dictionaries
    Dictionary<string, int> ages = new Dictionary<string, int>();
    Dictionary<int, string> products = new Dictionary<int, string>
    {
        [1] = "Laptop",
        [2] = "Mouse",
        [3] = "Keyboard"
    };
    
    // Adding elements
    ages["John"] = 25;
    ages.Add("Jane", 30);
    ages.TryAdd("Bob", 35); // Safe add
    
    // Accessing elements
    int johnsAge = ages["John"]; // 25
    
    // Safe access
    if (ages.ContainsKey("Alice"))
    {
        int alicesAge = ages["Alice"];
    }
    
    // Using TryGetValue (recommended)
    if (ages.TryGetValue("Jane", out int janesAge))
    {
        Console.WriteLine($"Jane's age: {janesAge}");
    }
    
    // Iterating
    foreach (KeyValuePair<string, int> entry in ages)
    {
        Console.WriteLine($"{entry.Key}: {entry.Value}");
    }
    
    // Just keys or values
    foreach (string name in ages.Keys)
    {
        Console.WriteLine(name);
    }
    
    foreach (int age in ages.Values)
    {
        Console.WriteLine(age);
    }
}

Advanced Dictionary Usage

csharp

public class DictionaryAdvanced
{
    public static void ComplexDictionaryExample()
    {
        // Dictionary with custom objects as keys
        Dictionary<Person, List<string>> personHobbies = 
            new Dictionary<Person, List<string>>();
        
        Person john = new Person { Name = "John", Age = 25 };
        personHobbies[john] = new List<string> { "Reading", "Swimming" };
        
        // Dictionary with case-insensitive keys
        Dictionary<string, string> config = 
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        
        config["Server"] = "localhost";
        Console.WriteLine(config["SERVER"]); // "localhost" - case insensitive
        
        // Counting occurrences
        string text = "hello world hello everyone";
        Dictionary<string, int> wordCount = new Dictionary<string, int>();
        
        foreach (string word in text.Split(' '))
        {
            if (wordCount.ContainsKey(word))
                wordCount[word]++;
            else
                wordCount[word] = 1;
        }
        
        // Using LINQ with dictionaries
        var frequentWords = wordCount
            .Where(pair => pair.Value > 1)
            .ToDictionary(pair => pair.Key, pair => pair.Value);
    }
}

// Custom class for dictionary key (must implement Equals and GetHashCode)
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    
    public override bool Equals(object obj)
    {
        return obj is Person person &&
               Name == person.Name &&
               Age == person.Age;
    }
    
    public override int GetHashCode()
    {
        return HashCode.Combine(Name, Age);
    }
}

HashSet<T> - Unique Elements

HashSet Operations

csharp

public static void DemonstrateHashSet()
{
    // Creating hash sets
    HashSet<int> uniqueNumbers = new HashSet<int> { 1, 2, 3, 4, 5 };
    HashSet<string> names = new HashSet<string>();
    
    // Adding elements (duplicates are ignored)
    uniqueNumbers.Add(3); // Still {1, 2, 3, 4, 5}
    uniqueNumbers.Add(6); // Now {1, 2, 3, 4, 5, 6}
    
    // Set operations
    HashSet<int> setA = new HashSet<int> { 1, 2, 3, 4, 5 };
    HashSet<int> setB = new HashSet<int> { 4, 5, 6, 7, 8 };
    
    // Union - all elements from both sets
    HashSet<int> union = new HashSet<int>(setA);
    union.UnionWith(setB); // {1, 2, 3, 4, 5, 6, 7, 8}
    
    // Intersection - common elements
    HashSet<int> intersection = new HashSet<int>(setA);
    intersection.IntersectWith(setB); // {4, 5}
    
    // Difference - elements in A but not in B
    HashSet<int> difference = new HashSet<int>(setA);
    difference.ExceptWith(setB); // {1, 2, 3}
    
    // Symmetric difference - elements in either but not both
    HashSet<int> symmetricDiff = new HashSet<int>(setA);
    symmetricDiff.SymmetricExceptWith(setB); // {1, 2, 3, 6, 7, 8}
    
    // Subset checks
    HashSet<int> smallSet = new HashSet<int> { 1, 2 };
    bool isSubset = smallSet.IsSubsetOf(setA); // true
    bool isProperSubset = smallSet.IsProperSubsetOf(setA); // true
    bool overlaps = smallSet.Overlaps(setB); // false
}

Practical HashSet Examples

csharp

public class HashSetApplications
{
    public static void RemoveDuplicates()
    {
        List<string> duplicateNames = new List<string> 
        { "John", "Jane", "John", "Bob", "Jane" };
        
        HashSet<string> uniqueNames = new HashSet<string>(duplicateNames);
        // Result: "John", "Jane", "Bob"
        
        List<string> uniqueList = uniqueNames.ToList();
    }
    
    public static void FindCommonElements()
    {
        List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
        List<int> list2 = new List<int> { 4, 5, 6, 7, 8 };
        
        HashSet<int> set1 = new HashSet<int>(list1);
        HashSet<int> set2 = new HashSet<int>(list2);
        
        set1.IntersectWith(set2);
        // set1 now contains {4, 5} - common elements
    }
}

Queue<T> and Stack<T>

Queue (FIFO - First In First Out)

csharp

public static void DemonstrateQueue()
{
    Queue<string> queue = new Queue<string>();
    
    // Enqueue - add to end
    queue.Enqueue("First");
    queue.Enqueue("Second");
    queue.Enqueue("Third");
    
    // Peek - look at front without removing
    string front = queue.Peek(); // "First"
    
    // Dequeue - remove from front
    while (queue.Count > 0)
    {
        string item = queue.Dequeue();
        Console.WriteLine(item);
    }
    // Output: First, Second, Third
    
    // Practical example - task processing
    Queue<Action> tasks = new Queue<Action>();
    tasks.Enqueue(() => Console.WriteLine("Task 1"));
    tasks.Enqueue(() => Console.WriteLine("Task 2"));
    
    while (tasks.Count > 0)
    {
        tasks.Dequeue()(); // Execute task
    }
}

Stack (LIFO - Last In First Out)

csharp

public static void DemonstrateStack()
{
    Stack<string> stack = new Stack<string>();
    
    // Push - add to top
    stack.Push("First");
    stack.Push("Second");
    stack.Push("Third");
    
    // Peek - look at top without removing
    string top = stack.Peek(); // "Third"
    
    // Pop - remove from top
    while (stack.Count > 0)
    {
        string item = stack.Pop();
        Console.WriteLine(item);
    }
    // Output: Third, Second, First
    
    // Practical example - undo functionality
    Stack<string> documentStates = new Stack<string>();
    
    documentStates.Push("Initial state");
    documentStates.Push("After typing");
    documentStates.Push("After formatting");
    
    // Undo last action
    string currentState = documentStates.Pop(); // Back to "After typing"
}

Other Collection Types

LinkedList<T>

csharp

public static void DemonstrateLinkedList()
{
    LinkedList<string> list = new LinkedList<string>();
    
    // Adding elements
    LinkedListNode<string> first = list.AddFirst("First");
    LinkedListNode<string> last = list.AddLast("Last");
    LinkedListNode<string> middle = list.AddAfter(first, "Middle");
    
    // Navigation
    LinkedListNode<string> current = first;
    while (current != null)
    {
        Console.WriteLine(current.Value);
        current = current.Next;
    }
    
    // Efficient insertion/removal in middle
    list.Remove(middle);
    list.AddBefore(last, "New Middle");
}

Sorted Collections

csharp

public static void DemonstrateSortedCollections()
{
    // SortedList - maintains sorted order by key
    SortedList<string, int> sortedAges = new SortedList<string, int>();
    sortedAges.Add("Charlie", 25);
    sortedAges.Add("Alice", 30);
    sortedAges.Add("Bob", 28);
    
    // Automatically sorted by key: Alice=30, Bob=28, Charlie=25
    foreach (var entry in sortedAges)
    {
        Console.WriteLine($"{entry.Key}: {entry.Value}");
    }
    
    // SortedDictionary - better for large datasets with frequent inserts
    SortedDictionary<string, int> sortedDict = new SortedDictionary<string, int>();
    sortedDict["Zoe"] = 22;
    sortedDict["Mike"] = 35;
    // Also automatically sorted
    
    // SortedSet - maintains sorted unique elements
    SortedSet<int> sortedNumbers = new SortedSet<int> { 5, 1, 8, 3, 2 };
    // Result: 1, 2, 3, 5, 8
}

Custom Generic Types

Generic Classes

csharp

// Simple generic class
public class Repository<T>
{
    private List<T> _items = new List<T>();
    
    public void Add(T item)
    {
        _items.Add(item);
    }
    
    public T Get(int index)
    {
        return _items[index];
    }
    
    public List<T> GetAll()
    {
        return new List<T>(_items);
    }
}

// Generic with constraints
public class Repository<T> where T : class, new()
{
    private List<T> _items = new List<T>();
    
    public T Create()
    {
        return new T();
    }
}

// Usage
Repository<string> stringRepo = new Repository<string>();
Repository<Person> personRepo = new Repository<Person>();

Generic Methods

csharp

public class Utility
{
    // Generic method in non-generic class
    public static T Max<T>(T a, T b) where T : IComparable<T>
    {
        return a.CompareTo(b) > 0 ? a : b;
    }
    
    public static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    
    // Generic method with multiple parameters
    public static TOutput Convert<TInput, TOutput>(TInput input, 
        Converter<TInput, TOutput> converter)
    {
        return converter(input);
    }
}

// Usage
int max = Utility.Max(10, 20); // 20
string result = Utility.Convert(123, num => num.ToString());

Advanced Generic Constraints

csharp

public class AdvancedGeneric<T> where T : 
    class,                    // Reference type
    IComparable,              // Implements interface
    new()                     // Has parameterless constructor
{
    public T CreateAndCompare(T other)
    {
        T newInstance = new T();
        return newInstance.CompareTo(other) > 0 ? newInstance : other;
    }
}

// Multiple type parameters with different constraints
public class Pair<TKey, TValue> 
    where TKey : IComparable<TKey>
    where TValue : class
{
    public TKey Key { get; set; }
    public TValue Value { get; set; }
}

Practical Collection Examples

Inventory Management System

csharp

public class InventorySystem
{
    private Dictionary<string, Product> _products = new Dictionary<string, Product>();
    private Queue<Order> _pendingOrders = new Queue<Order>();
    private HashSet<string> _categorySet = new HashSet<string>();
    
    public void AddProduct(string sku, Product product)
    {
        _products[sku] = product;
        _categorySet.Add(product.Category);
    }
    
    public void PlaceOrder(Order order)
    {
        _pendingOrders.Enqueue(order);
    }
    
    public Order ProcessNextOrder()
    {
        if (_pendingOrders.Count > 0)
            return _pendingOrders.Dequeue();
        return null;
    }
    
    public List<string> GetAllCategories()
    {
        return _categorySet.ToList();
    }
    
    public Dictionary<string, int> GetStockLevels()
    {
        return _products.ToDictionary(p => p.Key, p => p.Value.StockQuantity);
    }
}

public class Product
{
    public string Name { get; set; }
    public string Category { get; set; }
    public decimal Price { get; set; }
    public int StockQuantity { get; set; }
}

public class Order
{
    public string OrderId { get; set; }
    public Dictionary<string, int> Items { get; set; } // SKU -> Quantity
}

Cache Implementation with Generics

csharp

public class GenericCache<TKey, TValue>
{
    private readonly Dictionary<TKey, CacheItem<TValue>> _cache = 
        new Dictionary<TKey, CacheItem<TValue>>();
    private readonly int _maxSize;
    private readonly Queue<TKey> _accessOrder = new Queue<TKey>();
    
    public GenericCache(int maxSize = 100)
    {
        _maxSize = maxSize;
    }
    
    public void Add(TKey key, TValue value, TimeSpan expiration)
    {
        if (_cache.Count >= _maxSize)
        {
            RemoveOldest();
        }
        
        _cache[key] = new CacheItem<TValue>(value, DateTime.UtcNow + expiration);
        _accessOrder.Enqueue(key);
    }
    
    public bool TryGet(TKey key, out TValue value)
    {
        if (_cache.TryGetValue(key, out CacheItem<TValue> item))
        {
            if (item.Expiration > DateTime.UtcNow)
            {
                value = item.Value;
                _accessOrder.Enqueue(key); // Update access order
                return true;
            }
            else
            {
                _cache.Remove(key);
            }
        }
        
        value = default(TValue);
        return false;
    }
    
    private void RemoveOldest()
    {
        while (_accessOrder.Count > 0)
        {
            TKey oldestKey = _accessOrder.Dequeue();
            if (_cache.ContainsKey(oldestKey))
            {
                _cache.Remove(oldestKey);
                break;
            }
        }
    }
    
    private class CacheItem<T>
    {
        public T Value { get; }
        public DateTime Expiration { get; }
        
        public CacheItem(T value, DateTime expiration)
        {
            Value = value;
            Expiration = expiration;
        }
    }
}

// Usage
var stringCache = new GenericCache<string, string>();
stringCache.Add("key1", "value1", TimeSpan.FromMinutes(10));

var complexCache = new GenericCache<int, List<Person>>();
complexCache.Add(1, new List<Person>(), TimeSpan.FromHours(1));

Performance Characteristics Summary

Collection Add Access Search Remove Best For
List<T> O(1)* O(1) O(n) O(n) Dynamic arrays, sequential access
Dictionary<K,V> O(1) O(1) O(1) O(1) Key-value lookups
HashSet<T> O(1) O(1) O(1) O(1) Unique elements, set operations
Queue<T> O(1) N/A O(n) O(1) FIFO processing
Stack<T> O(1) N/A O(n) O(1) LIFO processing
LinkedList<T> O(1) O(n) O(n) O(1) Frequent insertions/deletions

*O(1) amortized for List<T>.Add

Best Practices

  1. Choose the right collection for your use case

  2. Use TryGetValue for dictionary access to avoid exceptions

  3. Prefer foreach over for for iterating collections when possible

  4. Consider capacity for lists and dictionaries to reduce resizing

  5. Use appropriate comparers for case-insensitive dictionaries

  6. Implement Equals and GetHashCode for custom key types

  7. Use generic constraints to ensure type safety

  8. Consider thread safety - most collections are not thread-safe by default

This comprehensive guide to C# Collections and Generics provides the foundation for working efficiently with data structures in C#!

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?