Skip to main content

Command Palette

Search for a command to run...

Mastering C# Part 3.3 Indexers & Properties

Complete Indexers & Properties concept with examples

Updated
4 min read
Mastering C# Part 3.3 Indexers & Properties

Indexers

Indexers allow instances of a class or struct to be accessed using array-like syntax. They provide a way to create classes that work as collections.

Why Use Indexers?

  • To encapsulate data structures like arrays or lists.

  • To provide custom access logic for accessing elements.

Declaration

public dataType this[int index]
{
    get { /* logic for returning value */ }
    set { /* logic for assigning value */ }
}

Key Features of Indexers

  • Access Modifiers: You can specify different access levels for get and set. For example, get can be public, and set can be protected.

  • Custom Logic: You can include validation or transformation logic within indexer accessors.

  • No Static Indexers: Indexers cannot be static.

Advanced Example

Here’s an example demonstrating validation and custom logic:

using System;

class CustomCollection
{
    private int[] _data = new int[5];

    public int this[int index]
    {
        get
        {
            if (index < 0 || index >= _data.Length)
                throw new IndexOutOfRangeException("Invalid index");
            return _data[index];
        }
        set
        {
            if (index < 0 || index >= _data.Length)
                throw new IndexOutOfRangeException("Invalid index");
            if (value < 0)
                throw new ArgumentException("Value cannot be negative");
            _data[index] = value;
        }
    }
}

class Program
{
    static void Main()
    {
        var collection = new CustomCollection();
        collection[0] = 42; // Valid
        Console.WriteLine(collection[0]); // Output: 42

        // collection[5] = 10; // Throws IndexOutOfRangeException
    }
}

Multidimensional Indexers

Multidimensional indexers allow classes to act as multidimensional arrays.

Declaration

Multidimensional indexers require multiple parameters:

public dataType this[int index1, int index2]
{
    get { /* logic */ }
    set { /* logic */ }
}

Example: Managing a Matrix

using System;

class Matrix
{
    private int[,] _matrix;

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

    public int this[int row, int col]
    {
        get
        {
            if (row < 0 || row >= _matrix.GetLength(0) || col < 0 || col >= _matrix.GetLength(1))
                throw new IndexOutOfRangeException("Invalid indices");
            return _matrix[row, col];
        }
        set
        {
            if (row < 0 || row >= _matrix.GetLength(0) || col < 0 || col >= _matrix.GetLength(1))
                throw new IndexOutOfRangeException("Invalid indices");
            _matrix[row, col] = value;
        }
    }
}

class Program
{
    static void Main()
    {
        var matrix = new Matrix(3, 3);
        matrix[0, 0] = 5;
        matrix[1, 1] = 10;

        Console.WriteLine(matrix[0, 0]); // Output: 5
        Console.WriteLine(matrix[1, 1]); // Output: 10
    }
}

Overloading Indexers

Concept

You can define multiple indexers in a single class by overloading them with different parameter types or counts.

Example: Overloading Based on Parameter Type

using System;

class OverloadedIndexers
{
    private int[] intArray = new int[5];
    private string[] stringArray = new string[5];

    public int this[int index]
    {
        get => intArray[index];
        set => intArray[index] = value;
    }

    public string this[string key]
    {
        get
        {
            int index = Array.IndexOf(stringArray, key);
            return index != -1 ? stringArray[index] : null;
        }
        set
        {
            int index = Array.IndexOf(stringArray, null); // Find empty slot
            if (index != -1) stringArray[index] = value;
        }
    }
}

class Program
{
    static void Main()
    {
        var obj = new OverloadedIndexers();

        obj[0] = 42; // Integer index
        obj["First"] = "Hello"; // String key

        Console.WriteLine(obj[0]);       // Output: 42
        Console.WriteLine(obj["First"]); // Output: Hello
    }
}

Properties

1. Introduction

Properties encapsulate fields and provide logic while getting or setting their values.

Declaration:

public dataType PropertyName
{
    get { /* logic */ }
    set { /* logic */ }
}

2. Advanced Features

a. Backing Fields

You can define a private field to store property values.

private int _value;

public int Value
{
    get => _value;
    set => _value = value;
}

b. Auto-Implemented Properties

Properties with no explicit backing field.

public int Value { get; set; }

c. Read-Only Properties

Use get only:

public int ReadOnlyValue { get; } = 42;

d. Computed Properties

Properties can compute their values dynamically:

public int Square => Value * Value;

Restrictions on Properties

  1. Cannot Take Parameters: Unlike indexers, properties do not accept parameters.

  2. Overloading Is Not Allowed: You cannot overload properties with the same name.

  3. No Logic Outside Accessors: Properties cannot perform complex logic outside get and set.

  4. Static and Instance Properties: Static properties belong to the class, while instance properties belong to objects.


Practical Example Combining Properties and Indexers

using System;

class Library
{
    private string[] books = new string[10];

    public string[] Books
    {
        get => books;
        set => books = value;
    }

    public string this[int index]
    {
        get => books[index];
        set => books[index] = value;
    }

    public int BookCount => books.Length;
}

class Program
{
    static void Main()
    {
        var library = new Library();
        library[0] = "C# in Depth";
        library[1] = "Pro ASP.NET Core";

        Console.WriteLine($"Total Books: {library.BookCount}");
        Console.WriteLine($"First Book: {library[0]}");
    }
}