404 Not Found

404 Not Found


nginx

Collections and Generics

Generic Concepts and Motivation

Generics allow us to write type-parameterized code, achieving type safety and code reuse. Before generics, ArrayList stored object types; value types were boxed when added and unboxed when retrieved, which hurt performance and lacked compile-time type checking.

Example

CSHARP
using System;
using System.Collections;

ArrayList list = new ArrayList();
list.Add(42);
list.Add("hello");

int value = (int)list[0];

foreach (object item in list)
{
    Console.WriteLine(item);
}
▶ Try it Yourself
TEXT
42
hello

The generic version determines element types at compile time, requiring no boxing or unboxing, and errors are caught during compilation:

CSHARP
using System;
using System.Collections.Generic;

List<int> numbers = new List<int>();
numbers.Add(42);

foreach (int n in numbers)
{
    Console.WriteLine(n);
}
TEXT
42

List<T> Dynamic List

List<T> is the most commonly used generic collection, supporting a dynamically resizable ordered list with rich methods for adding, removing, searching, and modifying.

Example

CSHARP
using System;
using System.Collections.Generic;

List<string> fruits = new List<string> { "apple", "banana", "cherry" };

fruits.Add("date");
fruits.AddRange(new string[] { "elderberry", "fig" });
fruits.Insert(1, "blueberry");

bool hasApple = fruits.Contains("apple");
int idx = fruits.IndexOf("cherry");

string found = fruits.Find(f => f.StartsWith("b"));
List<string> allB = fruits.FindAll(f => f.StartsWith("b"));

fruits.Sort();
fruits.ForEach(f => Console.WriteLine(f));

Console.WriteLine("---");
Console.WriteLine($"Count: {fruits.Count}");
Console.WriteLine($"Has apple: {hasApple}");
Console.WriteLine($"Cherry index: {idx}");
Console.WriteLine($"First b-word: {found}");
Console.WriteLine($"All b-words: {string.Join(", ", allB)}");
▶ Try it Yourself
TEXT
apple
blueberry
banana
cherry
date
elderberry
fig
---
Count: 7
Has apple: True
Cherry index: 3
First b-word: blueberry
All b-words: blueberry, banana

Removing Elements

CSHARP
using System;
using System.Collections.Generic;

List<int> nums = new List<int> { 10, 20, 30, 40, 50 };

nums.Remove(30);
nums.RemoveAt(0);

Console.WriteLine(string.Join(", ", nums));
Console.WriteLine($"Count: {nums.Count}");
TEXT
20, 40, 50
Count: 3

Dictionary<TKey, TValue> Dictionary

Dictionary<TKey, TValue> stores key-value pairs, enabling fast value lookup by key with near O(1) lookup time.

Example

CSHARP
using System;
using System.Collections.Generic;

Dictionary<string, int> scores = new Dictionary<string, int>
{
    { "Alice", 90 },
    { "Bob", 85 }
};

scores.Add("Charlie", 78);
scores["Bob"] = 92;

bool exists = scores.ContainsKey("Alice");

if (scores.TryGetValue("Dave", out int daveScore))
{
    Console.WriteLine($"Dave: {daveScore}");
}
else
{
    Console.WriteLine("Dave not found");
}

scores.Remove("Charlie");

Console.WriteLine($"Count: {scores.Count}");
Console.WriteLine("---Keys---");
foreach (string key in scores.Keys)
{
    Console.WriteLine(key);
}
Console.WriteLine("---Values---");
foreach (int val in scores.Values)
{
    Console.WriteLine(val);
}
Console.WriteLine("---Pairs---");
foreach (KeyValuePair<string, int> kv in scores)
{
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}
▶ Try it Yourself
TEXT
Dave not found
Count: 2
---Keys---
Alice
Bob
---Values---
90
92
---Pairs---
Alice: 90
Bob: 92

Generic Methods and Generic Classes

Generics are not limited to collections; methods and classes can also use type parameters, making algorithms applicable to multiple types.

Example

CSHARP
using System;

T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) >= 0 ? a : b;
}

Console.WriteLine(Max(3, 7));
Console.WriteLine(Max("apple", "banana"));
▶ Try it Yourself
TEXT
7
banana

Generic class example:

CSHARP
using System;

class Box<T>
{
    private T _value;

    public Box(T value)
    {
        _value = value;
    }

    public T Value => _value;

    public override string ToString() => $"Box<{typeof(T).Name}>: {_value}";
}

Box<int> intBox = new Box<int>(42);
Box<string> strBox = new Box<string>("hello");

Console.WriteLine(intBox);
Console.WriteLine(strBox);
TEXT
Box<Int32>: 42
Box<String>: hello

Generic Constraints

Use the where keyword to impose constraints on type parameters, limiting the range of types that can be used.

Example

CSHARP
using System;

class Repository<T> where T : class, new()
{
    public T Create()
    {
        T instance = new T();
        Console.WriteLine($"Created: {typeof(T).Name}");
        return instance;
    }
}

class Player
{
    public string Name { get; set; } = "Unknown";
}

Repository<Player> repo = new Repository<Player>();
Player p = repo.Create();
Console.WriteLine($"Name: {p.Name}");
▶ Try it Yourself
TEXT
Created: Player
Name: Unknown

Common constraints reference:

Constraint Description
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 parameterless public constructor
where T : IFoo T must implement the IFoo interface
where T : Base T must inherit from the Base class

Queue<T> Queue

A queue is a first-in-first-out (FIFO) data structure. Use Enqueue to add and Dequeue to remove.

Example

CSHARP
using System;
using System.Collections.Generic;

Queue<string> queue = new Queue<string>();
queue.Enqueue("first");
queue.Enqueue("second");
queue.Enqueue("third");

string front = queue.Peek();
Console.WriteLine($"Peek: {front}");

while (queue.Count > 0)
{
    Console.WriteLine($"Dequeue: {queue.Dequeue()}");
}
▶ Try it Yourself
TEXT
Peek: first
Dequeue: first
Dequeue: second
Dequeue: third

Stack<T> Stack

A stack is a last-in-first-out (LIFO) data structure. Use Push to add and Pop to remove.

Example

CSHARP
using System;
using System.Collections.Generic;

Stack<int> stack = new Stack<int>();
stack.Push(10);
stack.Push(20);
stack.Push(30);

int top = stack.Peek();
Console.WriteLine($"Peek: {top}");

while (stack.Count > 0)
{
    Console.WriteLine($"Pop: {stack.Pop()}");
}
▶ Try it Yourself
TEXT
Peek: 30
Pop: 30
Pop: 20
Pop: 10

Collection Initializers

Collection initializers allow you to populate elements directly when creating a collection, with concise and intuitive syntax.

Example

CSHARP
using System;
using System.Collections.Generic;

List<int> nums = new List<int> { 1, 2, 3, 4, 5 };
Dictionary<string, double> prices = new Dictionary<string, double>
{
    { "coffee", 3.5 },
    { "tea", 2.8 },
    { "juice", 4.0 }
};

Console.WriteLine(string.Join(", ", nums));
foreach (var kv in prices)
{
    Console.WriteLine($"{kv.Key}: ${kv.Value}");
}
▶ Try it Yourself
TEXT
1, 2, 3, 4, 5
coffee: $3.5
tea: $2.8
juice: $4

Introduction to IEnumerable and Iterators

IEnumerable<T> is the foundational interface for all traversable collections, and the foreach loop relies on it. Using yield return, you can easily write custom iterators (details in Lesson 35).

Example

CSHARP
using System;
using System.Collections.Generic;

IEnumerable<int> GetRange(int start, int end)
{
    for (int i = start; i <= end; i++)
    {
        yield return i;
    }
}

foreach (int n in GetRange(1, 5))
{
    Console.WriteLine(n);
}
▶ Try it Yourself
TEXT
1
2
3
4
5
💡 yield return turns a method into an iterator: each call returns one value and pauses, and the next iteration resumes from the pause point. For a complete explanation, see Lesson 35.

❓ FAQ

Q What is the difference between List&lt;T> and arrays?
A List&lt;T> supports dynamically adding and removing elements, while arrays have a fixed length. List is internally backed by an array; when resizing is needed, it automatically creates a larger array and copies the elements.
Q What happens if you add a duplicate key to a Dictionary?
A Using the Add method with a duplicate key throws ArgumentException; using the indexer assignment dict[key] = value overwrites the existing value.
Q Can where T : class and where T : struct be used together?
A No, the class constraint requires a reference type and the struct constraint requires a value type — they are mutually exclusive.
Q Can Queue and Stack be traversed with foreach?
A Yes, foreach traversal does not remove elements; it only reads them. Dequeue and Pop are what actually remove elements.
Q Why is the generic constraint where T : new() needed?
A It guarantees that you can use new T() to create an instance inside a method. Without this constraint, the compiler does not allow using the new operator on T.

📖 Summary

📝 Exercises

  1. Create a List<double>, add 5 scores, use FindAll to filter scores above 80 and output them
  2. Create a Dictionary<string, string> storing 3 capital city-to-province mappings, implement looking up a province by city name (using TryGetValue)
  3. Write a generic method T Clamp<T>(T value, T min, T max) where T : IComparable<T> that clamps a value within the [min, max] range
  4. Use Queue<string> to simulate a print queue: enqueue 3 document names, then dequeue and print them in order
  5. Write an IEnumerable<int> iterator method using yield return to generate the first 10 terms of the Fibonacci sequence
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏