Indexer And Enumerators In C# Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    7 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of Indexer and Enumerators in C#

Indexers and Enumerators in C#

Indexers

What is an Indexer?

  • An indexer in C# allows you to create a class that can be accessed like an array.
  • They give you the ability to define how an object’s instance fields and properties can be indexed or accessed.

Syntax:

public type this[int index] {
    get {
        // Code to get value
    }
    set {
        // Code to set value
    }
}
  • Replace type with the data type that the indexer will manage (e.g., string, int, etc.).
  • int index is the parameter used to identify which element to retrieve or assign. You can have more than one index if needed.

Example Usage:

public class IndexedNames {
    private string[] names = new string[10];
    
    public string this[int index] {
        get {
            return names[index];
        }
        set {
            names[index] = value;
        }
    }
}

IndexedNames myNames = new IndexedNames();
myNames[0] = "John";
Console.WriteLine(myNames[0]); // Outputs: John

Important Points about Indexers:

  1. Overloading: You can overload indexers by using different types or numbers of parameters.
  2. Read-Only/Write-Only: Indexers can be read-only (get only) or write-only (set only), but they must have at least one accessor.
  3. Multi-Dimensional: You can create multi-dimensional indexers which can mimic multi-dimensional arrays.
  4. Validation: Always validate the index in the setter to ensure it does not exceed the bounds of the underlying data structure.
  5. Performance: Indexers should be used carefully with respect to performance since each indexing can involve method calls.

Enumerators

What is an Enumerator?

  • In C#, an enumerator is an object responsible for iterating through a collection of items and provides access to each item in the sequence.
  • It implements the IEnumerator interface, which defines standard methods and properties, such as MoveNext(), Reset(), and Current.

Standard Interface:

public interface IEnumerator {
    bool MoveNext();         // Advances the enumerator to the next element.
    void Reset();            // Sets the enumerator to its initial position, which is before the first element in the collection.
    object Current { get; }  // Gets the current element in the collection.
}

Generic Version: Starting from C# 2.0, prefer the generic version IEnumerator<T> for better type safety.

public interface IEnumerator<out T> : IDisposable, IEnumerator {
    T Current { get; }      // Gets the current element in the collection.
}

Example Using foreach: The foreach loop internally uses an enumerator to iterate over collections.

using System.Collections;

public class CustomCollection : IEnumerable {
    private string[] colors = { "Red", "Green", "Blue", "Yellow", "Black" };
    
    public IEnumerator GetEnumerator() {
        foreach (string color in colors) {
            yield return color;
        }
    }
}

CustomCollection cc = new CustomCollection();
foreach (string color in cc) {
    Console.WriteLine(color);
}

Using yield: From C# 2.0 onwards, you can simplify the creation of enumerators using the yield keyword.

  • yield return returns an item in the sequence.
  • yield break stops iteration and exits the iterator block.
  • The compiler generates the necessary code to implement the IEnumerator interface.

Implementing IEnumerable: You typically implement the IEnumerable interface to indicate that your class can be enumerated.

public class CustomCollection : IEnumerable<string> {
    private string[] colors = { "Red", "Green", "Blue", "Yellow", "Black" };
    
    public IEnumerator<string> GetEnumerator() {
        foreach (string color in colors) {
            yield return color;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

Important Points about Enumerators:

  1. Statefulness: Enumerators maintain their state across the calls to MoveNext() and can be reset using the Reset() method.
  2. Type Safety: When possible, use the generic version IEnumerator<T> for type-safe iteration.
  3. Resource Management: Implementing IDisposable ensures that resources are properly released after enumeration is done.
  4. Conciseness: Use yield return to make enumerator implementations shorter and cleaner.
  5. Exception Handling: Handle exceptions such as InvalidOperationException if an attempt is made to access Current when enumerator is pointing outside the collection.
  6. Thread Safety: Enumeration itself is not thread safe, so concurrent modifications to the collection can lead to unexpected behavior.

Summary:

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement Indexer and Enumerators in C#

Indexers in C#

Indexers provide a way to access elements of a class using the same syntax as arrays. Let's create a simple example where we have a class NumberCollection that can hold a collection of integers and allows us to access them using an indexer.

Step 1: Define the Class

First, we define a class NumberCollection which will hold a list of integers.

using System;
using System.Collections.Generic;

public class NumberCollection
{
    // A private list to hold the numbers
    private List<int> _numbers = new List<int>();

    // An indexer to access the numbers in the list
    public int this[int index]
    {
        get
        {
            // Check if the index is within the valid range
            if (index >= 0 && index < _numbers.Count)
            {
                return _numbers[index];
            }
            else
            {
                throw new IndexOutOfRangeException("Index out of range");
            }
        }
        set
        {
            // Check if the index is within the valid range
            if (index >= 0 && index < _numbers.Count)
            {
                _numbers[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException("Index out of range");
            }
        }
    }

    // Method to add a new number to the collection
    public void Add(int number)
    {
        _numbers.Add(number);
    }
}

class Program
{
    static void Main()
    {
        // Create an instance of NumberCollection
        NumberCollection numbers = new NumberCollection();

        // Add numbers to the collection
        numbers.Add(10);
        numbers.Add(20);
        numbers.Add(30);

        // Access and modify numbers using the indexer
        Console.WriteLine(numbers[0]); // Output: 10
        numbers[0] = 100;
        Console.WriteLine(numbers[0]); // Output: 100

        // Attempt to access an out-of-range index
        try
        {
            Console.WriteLine(numbers[10]);
        }
        catch (IndexOutOfRangeException ex)
        {
            Console.WriteLine(ex.Message); // Output: Index out of range
        }
    }
}

Explanation:

  1. Class Definition: NumberCollection is a class that contains a private list of integers.
  2. Indexer Declaration: The indexer this[int index] allows access to elements in the _numbers list by using square brackets ([]).
  3. Get and Set Accessors: Inside the indexer, we use get and set to access and modify elements. We also validate the index to ensure it is within the bounds of the list.
  4. Add Method: The Add method is used to add new numbers to the collection.
  5. Main Method: In the Main method, we create an instance of NumberCollection, add three numbers, and then use the indexer to access and modify elements. We also demonstrate handling an IndexOutOfRangeException for an invalid index.

Enumerators in C#

Enumerators allow you to iterate through the elements of a collection. Let's create an example where we have a BookCollection class that implements an enumerator to iterate through a list of books.

Step 1: Define the Book Class

First, let's define a simple Book class.

public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }

    public Book(string title, string author)
    {
        Title = title;
        Author = author;
    }

    public override string ToString()
    {
        return $"{Title} by {Author}";
    }
}

Step 2: Define the BookCollection Class

Now, let's define the BookCollection class which will implement IEnumerator<Book> and IEnumerable<Book> to allow iteration.

Top 10 Interview Questions & Answers on Indexer and Enumerators in C#

Top 10 Questions and Answers on Indexers and Enumerators in C#

1. What are Indexers in C#, and how are they different from properties?

2. Can a class have multiple indexers?

Answer: Yes, a class can have multiple indexers. These are known as overloaded indexers. Overloaded indexers allow you to define different types of indexers for accessing different properties of a collection. Each indexer must have a unique signature, typically distinguished by the number or types of parameters.

public class SampleCollection
{
    private string[] items;

    // Indexer to access elements by integer index.
    public string this[int index]
    {
        get => items[index];
        set => items[index] = value;
    }

    // Indexer to access elements by string key.
    public string this[string key]
    {
        get => items[Array.IndexOf(items, key)];
        set => items[Array.IndexOf(items, key)] = value;
    }
}

3. What is an enumerator in C# and how does it work?

Answer: An enumerator in C# is an object that provides access to the elements in a collection one-at-a-time, without exposing the underlying structure of the collection. It typically includes the MoveNext() method to move to the next element, the Current property to get the current element, and the Reset() method to reset the enumerator to its starting position. The IEnumerator interface and the foreach statement simplify the process of enumerating over collections.

public class SampleCollection : IEnumerable<string>
{
    private string[] items;

    // Implementing IEnumerable<string>
    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        return new SampleEnumerator(items);
    }

    // Non-generic IEnumerable.GetEnumerator implementation
    IEnumerator IEnumerable.GetEnumerator()
    {
        return new SampleEnumerator(items);
    }

    private class SampleEnumerator : IEnumerator<string>
    {
        private readonly string[] items;
        int index = -1;

        public SampleEnumerator(string[] items)
        {
            this.items = items;
        }

        public bool MoveNext()
        {
            if (++index >= items.Length) return false;
            return true;
        }

        public void Reset()
        {
            index = -1;
        }

        public string Current
        {
            get
            {
                if (index == -1 || index >= items.Length)
                    throw new InvalidOperationException();

                return items[index];
            }
        }

        object IEnumerator.Current
        {
            get { return Current; }
        }

        public void Dispose() { /* Implement IDisposable if necessary */ }
    }
}

4. Can you explain the purpose and usage of IEnumerator and IEnumerable interfaces?

Answer: IEnumerable is an interface that defines a single method, GetEnumerator(), which returns an object that implements the IEnumerator interface. IEnumerator provides the mechanism to traverse the objects in a collection. Key methods in IEnumerator include MoveNext(), Reset(), and Current. IEnumerable types can be used in foreach loops to iterate over the collection. IEnumerable<T> is a generic version of IEnumerable and IEnumerator<T> is a generic version of IEnumerator.

5. How do you create a custom indexer for a class in C#?

Answer: To create a custom indexer in C#, you define an this keyword inside a class or struct, followed by the type of the indexer parameter(s) in square brackets. The indexer property can include get and set accessors.

public class CustomCollection
{
    private List<int> items = new List<int>();

    // Custom indexer
    public int this[int index]
    {
        get
        {
            if (index < 0 || index >= items.Count) throw new ArgumentOutOfRangeException(nameof(index));
            return items[index];
        }
        set
        {
            if (index < 0 || index >= items.Count) throw new ArgumentOutOfRangeException(nameof(index));
            items[index] = value;
        }
    }
}

6. What is the difference between IEnumerator and Carsor in C#?

Answer: IEnumerator is an interface defined in the .NET Framework for iterating through a collection. It has methods like MoveNext(), Reset(), and a Current property. A "cursor" is a more generic term often used in the context of database operations, not specific to C#. In C#, you do not use a cursor for iterating through collections; instead, you use IEnumerator or foreach loops. If you have database operations in mind, "cursor" might refer to database cursors that allow you to navigate and manipulate rows in a database result set.

7. What is the role of the foreach loop in relation to indexers and enumerators?

Answer: The foreach loop in C# simplifies the process of iterating over elements in a collection by using the IEnumerable and IEnumerator interfaces. It automatically handles the creation and disposal of the enumerator. foreach is particularly useful when you want to iterate over collections with indexers or enumerators without worrying about the underlying implementation details.

foreach (var item in collection)
{
    Console.WriteLine(item);
}

8. How can you ensure that a class implements IEnumerable<T> properly?

Answer: To ensure that a class implements IEnumerable<T> properly, you need to:

  • Implement the GetEnumerator() method that returns an IEnumerator<T>.
  • Provide a MoveNext() method that advances the enumerator to the next element.
  • Provide a Current property that returns the current element in the collection.
  • Consider implementing IDisposable if managing resources such as file handles or network connections.
  • Optionally, implement a non-generic GetEnumerator() method by casting.
public class CustomCollection<T> : IEnumerable<T>
{
    private List<T> items = new List<T>();

    public IEnumerator<T> GetEnumerator()
    {
        foreach (T item in items)
        {
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

9. How can you use indexers with multidimensional arrays in C#?

Answer: In C#, you can define indexers for multidimensional arrays by including multiple parameters in the indexer definition. This allows you to access elements in a two-dimensional (or higher) array structure.

public class Matrix
{
    private int[,] items;

    public Matrix(int rows, int cols)
    {
        items = new int[rows, cols];
    }

    // Multi-dimensional indexer
    public int this[int row, int col]
    {
        get => items[row, col];
        set => items[row, col] = value;
    }
}

10. What are the advantages of using indexers over methods for accessing elements in a collection?

Answer: Using indexers over methods for accessing elements in a collection offers several advantages:

  • Syntactic Sugar: Indexers provide a more natural and concise way to access elements, making code easier to read and write.
  • Semantic Readability: Indexers help convey the intent of accessing an element at a specific position, enhancing code readability.
  • Familiar Syntax: Indexers use a syntax similar to arrays, which is familiar to most programmers.
  • Overloading: Multiple indexers can be defined, allowing complex indexing schemes.

You May Like This Related .NET Topic

Login to post a comment.