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
-
Choose the right collection for your use case
-
Use
TryGetValuefor dictionary access to avoid exceptions -
Prefer
foreachoverforfor iterating collections when possible -
Consider capacity for lists and dictionaries to reduce resizing
-
Use appropriate comparers for case-insensitive dictionaries
-
Implement
EqualsandGetHashCodefor custom key types -
Use generic constraints to ensure type safety
-
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!