What are properties and indexers in C#?

8 minbeginner.NETpropertiesindexersC#

Quick Answer

Properties provide controlled access to private fields with get/set accessors. They can be auto-properties, expression-bodied, or init-only. Indexers allow objects to be indexed like arrays using square bracket notation. Properties enable encapsulation and validation, while indexers make objects behave like collections. Use properties for data access, indexers for collection-like behavior.

Detailed Answer

Properties are members that provide a flexible mechanism to read, write, or compute the values of private fields. They act as intermediaries between the outside world and the internal state of a class.

Basic Property Syntax:

public class Person
{
    private string _name;
    private int _age;
    
    // Traditional property with backing field
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    
    // Auto-property (C# 3.0+)
    public int Age { get; set; }
    
    // Read-only auto-property
    public DateTime CreatedAt { get; } = DateTime.Now;
    
    // Property with validation
    public string Email
    {
        get => _email;
        set
        {
            if (string.IsNullOrEmpty(value) || !value.Contains("@"))
                throw new ArgumentException("Invalid email format");
            _email = value;
        }
    }
    private string _email;
}

Property Types:

1. Auto-Properties:

public class Product
{
    // Auto-property with getter and setter
    public string Name { get; set; }
    
    // Read-only auto-property
    public int Id { get; }
    
    // Auto-property with initializer
    public decimal Price { get; set; } = 0m;
    
    // Auto-property with different access modifiers
    public string Description { get; private set; }
}

2. Expression-Bodied Properties (C# 6.0+):

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    // Expression-bodied property
    public double Area => Width * Height;
    
    // Expression-bodied property with getter/setter
    public double Perimeter
    {
        get => 2 * (Width + Height);
        set => Width = Height = value / 4; // Square
    }
}

3. Init-Only Properties (C# 9.0+):

public class User
{
    // Can only be set during object initialization
    public string FirstName { get; init; }
    public string LastName { get; init; }
    
    // Computed property
    public string FullName => $"{FirstName} {LastName}";
}

// Usage
var user = new User
{
    FirstName = "John",
    LastName = "Doe"
    // Cannot set FirstName after initialization
    // user.FirstName = "Jane"; // Compile error
};

Indexers: Indexers allow objects to be indexed like arrays, providing a way to access elements using square bracket notation.

Basic Indexer:

public class StringCollection
{
    private string[] _items = new string[10];
    
    // Indexer
    public string this[int index]
    {
        get
        {
            if (index < 0 || index >= _items.Length)
                throw new IndexOutOfRangeException();
            return _items[index];
        }
        set
        {
            if (index < 0 || index >= _items.Length)
                throw new IndexOutOfRangeException();
            _items[index] = value;
        }
    }
}

// Usage
StringCollection collection = new StringCollection();
collection[0] = "Hello";
collection[1] = "World";
Console.WriteLine(collection[0]); // "Hello"

Multi-dimensional Indexers:

public class Matrix
{
    private int[,] _matrix;
    
    public Matrix(int rows, int columns)
    {
        _matrix = new int[rows, columns];
    }
    
    // Multi-dimensional indexer
    public int this[int row, int column]
    {
        get => _matrix[row, column];
        set => _matrix[row, column] = value;
    }
}

// Usage
Matrix matrix = new Matrix(3, 3);
matrix[0, 0] = 1;
matrix[1, 1] = 2;

String-based Indexers:

public class Dictionary
{
    private Dictionary<string, string> _items = new();
    
    // String-based indexer
    public string this[string key]
    {
        get => _items.TryGetValue(key, out string value) ? value : null;
        set => _items[key] = value;
    }
}

// Usage
Dictionary dict = new Dictionary();
dict["name"] = "John";
dict["age"] = "30";
Console.WriteLine(dict["name"]); // "John"

Properties vs Fields:

public class Example
{
    // Field - direct access to memory
    public string FieldName;
    
    // Property - controlled access with logic
    private string _propertyName;
    public string PropertyName
    {
        get => _propertyName;
        set => _propertyName = value?.Trim();
    }
}

Key Differences:

  • Fields: Direct memory access, no validation, no side effects
  • Properties: Controlled access, validation, side effects, encapsulation

Best Practices:

  1. Use properties for public data access
  2. Use fields only for private implementation details
  3. Use auto-properties when no validation is needed
  4. Use expression-bodied properties for simple computations
  5. Use init-only properties for immutable data
  6. Validate input in property setters
  7. Use indexers when your class represents a collection

Related Resources