What are properties and indexers in C#?
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:
- Use properties for public data access
- Use fields only for private implementation details
- Use auto-properties when no validation is needed
- Use expression-bodied properties for simple computations
- Use init-only properties for immutable data
- Validate input in property setters
- Use indexers when your class represents a collection