Properties and Indexers
Properties are C#'s idiomatic mechanism for exposing an object's state while keeping the ability to intercept reads and writes with logic — validation, lazy computation, change notification, or simply future-proofing an API. Syntactically a property looks like a public field to callers (customer.Name = "Ada";), but under the hood the compiler generates get_Name() and set_Name(value) methods, so you can add behavior later without breaking binary or source compatibility for consumers. This is a deliberate improvement over exposing raw public fields, which lock you into never being able to add validation or computed logic without a breaking API change.
Cricket analogy: A property is like a scoreboard display that looks like a static number to fans but is actually recalculated behind the scenes (get_Score) whenever a run is scored, so broadcast graphics never need to change even if scoring logic updates mid-tournament.
Auto-implemented and computed properties
When a property needs no custom logic, an auto-implemented property (public string Name { get; set; }) lets the compiler generate a private backing field for you automatically. Adding init instead of set (C# 9+) makes the property settable only during object initialization — in a constructor or an object initializer — which is the backbone of immutable record-style types. Expression-bodied properties (public int Area => Width * Height;) compute a value on every read without storing it, which is ideal for derived state that must always stay in sync with other fields.
Cricket analogy: An auto-property is like a scoreboard automatically tracking runs with no extra logic needed; init is like a player's jersey number settable only during squad registration, never changed mid-season; an expression-bodied property is like a live run-rate recalculated every ball from overs and runs, never stored.
public class Rectangle
{
private double _width;
public double Width
{
get => _width;
set => _width = value >= 0
? value
: throw new ArgumentOutOfRangeException(nameof(value), "Width cannot be negative.");
}
public double Height { get; init; }
// Computed, read-only property with no backing field.
public double Area => Width * Height;
// Indexer exposing corner points by index, like an array.
private readonly (double X, double Y)[] _corners = new (double, double)[4];
public (double X, double Y) this[int cornerIndex]
{
get => _corners[cornerIndex];
set => _corners[cornerIndex] = value;
}
}Indexers
An indexer lets instances of a class be indexed like an array, using this[...] accessors instead of a method name. Indexers can be overloaded with different parameter types (an integer index, a string key, even multiple parameters for a matrix-like grid[row, col]), and — like properties — can have asymmetric access (a public getter with a private or protected setter). Collections such as List<T> and Dictionary<TKey,TValue> use indexers internally to power the list[0] and dict["key"] syntax you already rely on.
Cricket analogy: An indexer is like accessing battingOrder[3] to get the number-3 batsman directly; it can be overloaded to accept a name instead (battingOrder["Kohli"]), and like a public scoreboard display with a private scorer-only edit function, it can allow public reads but restrict writes.
Unlike Java, which has no native property syntax and relies on manual getter/setter method pairs (getName()/setName()), C# properties are a first-class language and CLR feature — LINQ, data binding, and serializers all recognize properties directly, and reflection can enumerate them distinctly from fields and methods.
A common pitfall is putting expensive or side-effecting logic inside a property getter. Callers reasonably assume that reading a property is cheap and side-effect-free, similar to reading a field — if a getter performs a database call or heavy computation, prefer an explicit method (GetTotalAsync()) instead so the cost is visible at the call site.
Required and init-only properties
C# 11 added the required modifier, which forces callers to set a property via an object initializer (or explicitly in every constructor path) before the object is considered fully constructed — the compiler enforces this at compile time. Combined with init, required gives you the safety of mandatory constructor parameters with the readability of named object-initializer syntax, which is especially useful for records and DTOs with many optional and mandatory fields mixed together.
Cricket analogy: The required modifier is like a team-sheet rule that a bowling attack listing must include a designated new-ball bowler before it's accepted, combined with init it locks that assignment at squad announcement, never changeable mid-match, ideal for a match-day DTO.
- Properties look like fields to callers but compile down to get/set accessor methods, allowing logic to be added without breaking the public API.
- Auto-implemented properties generate a hidden backing field automatically;
initrestricts assignment to object construction time. - Expression-bodied properties compute a value on every access rather than storing it.
- Indexers (
this[...]) let custom types support array/dictionary-likeobj[key]syntax and can be overloaded by parameter type. - The
requiredmodifier (C# 11+) enforces that a property must be set during initialization, checked at compile time. - Property getters should stay cheap and side-effect-free; expensive operations belong in explicit methods.
Practice what you learned
1. What does the compiler generate for an auto-implemented property `public int Age { get; set; }`?
2. What is the effect of using `init` instead of `set` in a property declaration?
3. Which of these is a valid reason to prefer an explicit method over a property getter?
4. Can an indexer be overloaded with different parameter types in the same class?
5. What compile-time guarantee does the `required` modifier (C# 11+) provide?
Was this page helpful?
You May Also Like
Encapsulation and Access Modifiers
Understand how C#'s access modifiers — public, private, protected, internal, and their combinations — let you control visibility and enforce encapsulation boundaries.
Classes and Objects
Understand how C# classes define blueprints for reference-type objects, covering fields, methods, object creation, and the distinction between a class and its instances.
Nullable Reference Types
See how C# 8's nullable reference types add compile-time null-safety annotations on top of the runtime's existing nullable value type system.
Pattern Matching
Survey C#'s pattern matching features — type patterns, property patterns, relational and logical patterns, and switch expressions — for expressive, safe branching.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics