Friday, June 18, 2021

C# Indexer Explained in Detail

Understanding C# Indexers

A C# indexer allows instances of a class or struct to be used with an index to access:

  1. Members of the class or struct, or
  2. Items of a collection-like member (e.g., an array, list, or dictionary) contained within the class or struct.

This feature provides array-like syntax for accessing or modifying data, improving readability and usability by abstracting how the data is retrieved or stored.

Syntax of an Indexer

The syntax for defining an indexer is as follows:

<access-modifier> <return-type> this[<parameter-type> parameter]

{

    get { /* logic to retrieve the value */ }

    set { /* logic to set the value */ }

}

Key points about the syntax:

  • this keyword indicates that the indexer belongs to the current instance of the class.
  • The parameter inside the square brackets determines the type of index (e.g., intstring).
  • Indexers can have get and set accessors, similar to properties.

Facts About Indexers

  1. Indexers are defined inside a class or structure as a special property.
  2. An indexer has access modifier such as public, private, protected, or internal (like properties).
  3. The return type of an indexer is written after the access modifier (like properties).
  4. Indexer has no name but this keyword is used instead (unlike properties).
  5. The keyword this is followed by square brackets containing one or more parameters of the indexer. Indexer overloading is possible by changing parameter datatype or number of parameters. Note that Indexer has parameter like properties.
  6. Indexer parameters can be of any data type. It can be int, string, bool, custom class etc. The data type of the indexer is determined by the type of the collection or array being indexed.
  7. Similarly, Indexer return type can be of any data type. Suppose that a class has a list of Employees as class member. If indexer is used in this class then an employee can be accessed via int type employee id or string type employee id. Here parameter of Indexer will be int or string but the indexer return type will be Employee class type.
  8. Note that the return type of an indexer is determined by the type of elements stored in the collection, not the parameter type. So, if your class has a list of Employee objects and you use an indexer to access an Employee, the return type will be Employee, regardless of whether the indexer parameter is an int or a string (used to identify the employee). The return type is still the type of the elements within the collection (Employee in this case), not the type of the indexer parameter.
  9. Indexer has accessor block given by flower brackets (like properties).
  10. The indexer body contains get and set blocks(similar to general properties) to handle value retrieval and assignment.
  11. Indexers cannot be static(unlike property) because indexer belongs to an object of class.
  12. Differences from Properties: Indexer differs from general properties in two ways markedly:
    • Indexer has parameter(s) but general properties can have parameters.
    • Indexer cannot be static but general properties can be static.

Benefits of Using Indexers

  • Simplifies accessing and modifying class members.
  • Provides a unified way to manage properties with index-like behavior.

Key Takeaways

  • Indexers simplify accessing class members using indices.
  • Indexers can be overloaded and customized.
  • They are a powerful feature of C# for creating flexible, intuitive class designs.

Indexer Parameters

An indexer in C# can have one or more parameters. However, single-parameter indexers are the most common. The parameters specify how the indexer behaves, and their types and count determine how the indexer can be accessed.

Key Points:

  1. Single Parameter (Most Common):
    • Example: this[int index]
    • Allows you to access members using a single value, similar to arrays.
  2. Multiple Parameters:
    • C# allows indexers to have multiple parameters.
    • Example:

public object this[int row, int column]

{

    get { /* Return value based on row and column */ }

    set { /* Set value based on row and column */ }

}

    • This is useful for scenarios like representing a grid or table.
  1. Parameter Types:
    • Parameters can be of different data types (e.g., int, string, etc.).

Using multiple parameters of different types is supported, which can enhance the indexer's versatility.

Example 1: Accessing Members of a Class

In this example, we define an Employee class with an indexer to access its individual members:

using System;
namespace IndexerExample;
class Employee
{
    public int Id { get; set; }
    public string? Name { get; set; } = null!;
    public double Salary { get; set; } 
    public string? Designation { get; set; } = null!;
    // Parameterized constructor
    public Employee(int id, string name, double salary, string designation)

    {
        Id = id; 
        Name = name;
        Salary = salary;
        Designation = designation;
    }
    // Indexer to access members by index
    public object this[int index]
    {
        get
        {
            return index switch
            {
                0 => Id,
                1 => Name,
                2 => Salary,
                3 => Designation,
                _ => null,
            };
        }
        set
        {
            switch (index)
            {

                case 0: Id = (int)value; break; // Unboxing
                case 1: Name = value.ToString(); break;
                case 2: Salary = (double)value; break; // Unboxing
                case 3: Designation = value.ToString(); break;
            }
        }
    }
}

Program class:
using System;
namespace IndexerExample;
    class Program
    {
        static void Main(string[] args)
        {

            Employee emp = new Employee(1, "Amar", 20000.0, "Accountant");
            Console.WriteLine(emp[0]); // Outputs: 1
            Console.WriteLine(emp[1]); // Outputs: Amar
            Console.WriteLine(emp[2]); // Outputs: 20000
            Console.WriteLine(emp[3]); // Outputs: Accountant
        }
    }

Output:

1
Amar
20000
Accountant

This demonstrates how an indexer can simplify access to class members using indices.

Example 2: Accessing Items of a Collection-Like Member

In this example, we define an EmployeeDirectory class with an indexer to access employees stored in a list:

Part1:Create Employee class
class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public double Salary { get; set; }
        public string Designation { get; set; }
 
        public Employee(int id, string name, double salary, string designation)
        {
            Id = id;
            Name = name;
            Salary = salary;
            Designation = designation;
        }
    }

Part2:Create EmployeeDirectory class
    class EmployeeDirectory
    {
        private List<Employee> employees = new();
 
        // Add an employee to the directory
        public void AddEmployee(Employee employee)
        {
            employees.Add(employee);
        }
 
        // Indexer to access employees by index
        public Employee this[int index]
        {
            get
            {
                if (index >= 0 && index < employees.Count)
                    return employees[index];
                throw new IndexOutOfRangeException("Invalid index");
            }
        }
    }

Part3:Create Program class as client class
    class Program
    {
        static void Main(string[] args)
        {
            EmployeeDirectory directory = new EmployeeDirectory();
            directory.AddEmployee(new Employee(1, "John", 25000.0, "Manager"));
            directory.AddEmployee(new Employee(2, "Alice", 30000.0, "Developer"));
 
            Console.WriteLine(directory[0].Name); // Outputs: John
            Console.WriteLine(directory[1].Name); // Outputs: Alice
        }
    }

Output:

John

Alice

This demonstrates how an indexer can be used to provide direct access to collection elements.

Key Points to Remember

  1. Indexer Definition:
    • An indexer is defined using the this keyword and square brackets.
    • It can be overloaded by varying the parameter type or number.
    • Indexers cannot be static and must belong to an instance of a class or struct.
  2. Usage Scenarios:
    • Accessing individual members of a class or struct (e.g., properties like Id, Name in an Employee class).
    • Accessing elements of a collection-type member within a class or struct (e.g., items in a List or Dictionary).
  3. Parameters:
    • Indexers take one or more parameters, but typically only one parameter is used. Indexers can be overloaded by varying the parameter type or number.
    • The parameter can be of any data type (e.g., int, string).
  4. Get and Set Accessors:
    • Use the get accessor to retrieve data.
    • Use the set accessor to modify data.
  5. Flexibility:
    • Indexers improve code readability and allow classes to behave like arrays or dictionaries, abstracting data retrieval and storage.

By combining these features, indexers provide powerful and intuitive data access mechanisms in C#.


No comments:

Post a Comment

Hot Topics