Indexers in C# enable you to treat custom objects like arrays, providing an intuitive way to access elements while keeping the internal structure private. In this article, we’ll explore indexers step by step, with clear examples to help you apply the concept in real-world scenarios.
What Are Indexers?
An indexer is a special kind of property that allows you to access elements of a class or struct using array-like syntax. You define it using the this
keyword, along with an index type.
Why Use Indexers?
- Encapsulation: Hide the internal collection structure.
- Readability: Simplify access to elements without exposing unnecessary details.
- Flexibility: Customize how elements are retrieved or set.
Step 1: A Simple Read-Only Indexer
Let’s create a class OrderList
that holds an array of Order
objects. We’ll define an indexer to retrieve orders by their position in the array.
Full Example
// Define an Order class public class Order { public int Id { get; set; } public string Description { get; set; } } // Define OrderList with an indexer public class OrderList { private Order[] _orders; // Constructor to initialize orders public OrderList(Order[] orders) { _orders = orders; } // Read-only indexer public Order this[int index] { get { if (index < 0 || index >= _orders.Length) throw new IndexOutOfRangeException("Invalid index."); return _orders[index]; } } } // Program to demonstrate usage public class Program { public static void Main() { // Initialize an array of orders var orders = new Order[] { new Order { Id = 1, Description = "Laptop" }, new Order { Id = 2, Description = "Smartphone" }, new Order { Id = 3, Description = "Tablet" } }; // Create an OrderList var orderList = new OrderList(orders); // Access orders using the indexer Console.WriteLine(orderList[0].Description); // Output: Laptop Console.WriteLine(orderList[1].Description); // Output: Smartphone } }
Explanation
- The
this
Keyword: Used to define an indexer. - Type Safety: The
int
type ensures only valid indices are used. - Encapsulation: The
_orders
array is private, so the internal data structure isn’t exposed. - Validation: The indexer validates the index to prevent runtime errors.
Step 2: Adding a Custom Key to the Indexer
What if you want to retrieve an order by its Id
instead of its position? We can extend the indexer to accept custom keys like int
or GUID
.
Full Example
public class OrderList { private Order[] _orders; public OrderList(Order[] orders) { _orders = orders; } // Indexer with integer index public Order this[int index] { get { if (index < 0 || index >= _orders.Length) throw new IndexOutOfRangeException("Invalid index."); return _orders[index]; } } // Indexer with GUID key public Order this[Guid orderId] { get { var order = _orders.FirstOrDefault(o => o.Id == orderId.GetHashCode()); if (order == null) throw new KeyNotFoundException("Order not found."); return order; } } } // Program to demonstrate usage public class Program { public static void Main() { var orders = new Order[] { new Order { Id = 1, Description = "Laptop" }, new Order { Id = 2, Description = "Smartphone" }, new Order { Id = 3, Description = "Tablet" } }; var orderList = new OrderList(orders); // Access orders using integer index Console.WriteLine(orderList[1].Description); // Output: Smartphone // Access orders using GUID var guid = Guid.NewGuid(); var order = new Order { Id = guid.GetHashCode(), Description = "Monitor" }; Console.WriteLine(orderList[guid].Description); // Throws KeyNotFoundException } }
Why Use a Method Instead of an Indexer?
For custom keys, retrieving an element can be expensive, especially with large data sets. Using an indexer might create a false expectation of fast access. Instead, define a method for such lookups:
public Order FindById(Guid orderId) => _orders.FirstOrDefault(o => o.Id == orderId.GetHashCode());
Step 3: Real-World Example
The Dictionary<TKey, TValue>
class in .NET uses indexers to retrieve values by their keys:
var dictionary = new Dictionary<int, string> { [1] = "Laptop", [2] = "Smartphone" }; Console.WriteLine(dictionary[1]); // Output: Laptop
Best Practices for Indexers
- Use Indexers for Fast Access: Ensure the underlying structure allows efficient retrieval.
- Avoid Complex Lookups: Use methods for computationally expensive operations.
- Provide Clear Error Messages: Validate indices and keys, throwing appropriate exceptions.
Top comments (0)