Exploring Indexers in C#: Array-Like Access for Custom Types
mohamed Tayel
Posted on November 13, 2024
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.
Posted on November 13, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.