C# tutorials > Core C# Fundamentals > Data Structures and Collections > How do you implement custom collection classes?
How do you implement custom collection classes?
Creating custom collection classes in C# allows you to tailor data structures to your specific needs. This tutorial provides a comprehensive guide on implementing custom collection classes, covering various aspects from basic implementation to advanced considerations. We'll explore the necessary interfaces, methods, and best practices for building efficient and maintainable custom collections.
Basic Implementation using `IEnumerable` and `IEnumerator`
This code demonstrates a simple custom collection class `PeopleCollection` that stores a list of `Person` objects. It implements `IEnumerable `IEnumerable `IEnumerator Explanation of Key Parts:
using System;
using System.Collections;
using System.Collections.Generic;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
public class PeopleCollection : IEnumerable<Person>
{
private List<Person> _people = new List<Person>();
public void Add(Person person)
{
_people.Add(person);
}
public IEnumerator<Person> GetEnumerator()
{
return new PeopleEnumerator(_people);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private class PeopleEnumerator : IEnumerator<Person>
{
private List<Person> _people;
private int _position = -1;
public PeopleEnumerator(List<Person> people)
{
_people = people;
}
public Person Current
{
get
{
if (_position < 0 || _position >= _people.Count)
{
throw new InvalidOperationException();
}
return _people[_position];
}
}
object IEnumerator.Current => Current;
public void Dispose()
{
//No resources to release
}
public bool MoveNext()
{
_position++;
return _position < _people.Count;
}
public void Reset()
{
_position = -1;
}
}
}
public class Example
{
public static void Main(string[] args)
{
PeopleCollection people = new PeopleCollection();
people.Add(new Person("Alice", 30));
people.Add(new Person("Bob", 25));
people.Add(new Person("Charlie", 35));
foreach (Person person in people)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
}
Concepts Behind the Snippet
The core concept involves separating the collection's data from the iteration logic. `IEnumerable` defines that a class can be iterated over, while `IEnumerator` handles how the iteration is performed. This separation of concerns makes the code more modular and maintainable. Key principles:
Real-Life Use Case Section
Imagine you're building a customer relationship management (CRM) system. You might have a collection of `Customer` objects. You could create a custom collection class for managing these customers. You might want to iterate through customers based on specific criteria (e.g., active customers, customers in a specific region). A custom collection allows you to encapsulate this logic and provide a clean, type-safe way to access your data. Example: A gaming application that displays a list of available games. The custom collection would maintain this list and handle tasks such as filtering, sorting, and updating the list dynamically.
Best Practices
Interview Tip
When asked about custom collections in interviews, highlight the importance of understanding `IEnumerable` and `IEnumerator`. Explain how these interfaces enable iteration and how custom implementations allow you to tailor data structures to specific application requirements. Be prepared to discuss the benefits of using custom collections (e.g., type safety, encapsulation) and the potential drawbacks (e.g., increased complexity). Also, be ready to discuss thread safety considerations.
When to Use Them
Use custom collections when:
Memory Footprint
The memory footprint of a custom collection depends on the underlying data structure used to store the data. For example, if you use a `List
Alternatives
Alternatives to creating custom collections include:
Pros
Cons
Implementing `ICollection` for Full Functionality
Implementing `ICollection Key aspects of `ICollection
using System;
using System.Collections;
using System.Collections.Generic;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
public class PeopleCollection : ICollection<Person>
{
private List<Person> _people = new List<Person>();
public int Count => _people.Count;
public bool IsReadOnly => false; // Or true if you want it read-only
public void Add(Person person)
{
_people.Add(person);
}
public void Clear()
{
_people.Clear();
}
public bool Contains(Person person)
{
return _people.Contains(person);
}
public void CopyTo(Person[] array, int arrayIndex)
{
_people.CopyTo(array, arrayIndex);
}
public bool Remove(Person person)
{
return _people.Remove(person);
}
public IEnumerator<Person> GetEnumerator()
{
return _people.GetEnumerator(); // Use List's built-in enumerator
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public class Example
{
public static void Main(string[] args)
{
PeopleCollection people = new PeopleCollection();
people.Add(new Person("Alice", 30));
people.Add(new Person("Bob", 25));
people.Add(new Person("Charlie", 35));
Console.WriteLine($"Count: {people.Count}");
foreach (Person person in people)
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
Person[] personArray = new Person[people.Count];
people.CopyTo(personArray, 0);
Console.WriteLine($"Array Length: {personArray.Length}");
people.Remove(new Person("Bob",25));
Console.WriteLine($"Count after Remove: {people.Count}");
people.Clear();
Console.WriteLine($"Count after Clear: {people.Count}");
}
}
}
FAQ
-
What interfaces are essential for implementing custom collections in C#?
The most essential interfaces are `IEnumerable` and `IEnumerator `. `IEnumerable ` enables iteration using `foreach` loops, while `IEnumerator ` provides the actual iteration logic. Implementing `ICollection ` adds support for common collection operations like `Add`, `Remove`, and `Clear`. -
How do I ensure thread safety in a custom collection?
Thread safety can be achieved by using locking mechanisms (e.g., `lock` keyword) to synchronize access to the collection's data. Alternatively, you can use thread-safe collection classes provided by the .NET Framework (e.g., `ConcurrentBag`, `ConcurrentDictionary`). -
Can I use `yield return` to simplify the implementation of `GetEnumerator()`?
Yes, `yield return` provides a concise way to implement the `GetEnumerator()` method. It automatically generates the `IEnumerator` implementation for you, simplifying the code and improving readability. This is often the preferred way for simple enumerations. -
What are the main differences between `ICollection
` and `IList `?
`ICollection` is a more general interface representing a collection of objects, providing basic operations like `Add`, `Remove`, `Contains`, and `CopyTo`. `IList `, on the other hand, extends `ICollection ` and represents an ordered collection of objects accessible by index, adding functionalities like `Insert`, `RemoveAt`, and indexer access.