C# Properties and Encapsulation: Read Clearly, Change Safely
Properties let callers read useful values without exposing every write. Learn where accessor code runs, how validation protects stored state, and when a guarded method is clearer than a public setter.
What you will understand
- Distinguish fields from properties and accessor behavior.
- Read and assign an automatic property without assuming it validates input.
- Use value and a backing field to validate before writing.
- Choose public read access with a private setter or getter-only property.
- Compute derived availability from current stock.
- Explain init-only timing without confusing it with validation.
- Find constructor paths that bypass accessor rules.
- Test both permitted operations and rejected external assignments.
Property syntax looks like data access but invokes accessors
A field is storage. A property is a member with accessors that expose a value, control assignment or calculate a result. The caller reads label.Name and assigns label.Name = "Pen" without parentheses, but get and set define what those operations mean.
get returns the value requested by a read. set receives the new assignment value through the contextual keyword value. That value has the property’s declared type. A property can provide only a getter, or can make its setter less accessible than its getter when that matches the intended interface.
An automatic property such as public string Name { get; set; } uses compiler-generated backing storage. Its initializer supplies a chosen starting value. Do not mistake compact syntax for validation: an unrestricted automatic integer setter will happily accept a negative count unless you add a rule or remove that write path.
Program.cs · Read and assign an automatic property
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen
Assigning Name uses its set accessor; reading Name uses get. The compiler supplies hidden storage for this automatic property. Callers use property syntax without parentheses, but the member is not a public field.
Program.cs · Automatic get and set do not add a validation rule
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
-3
An unrestricted automatic setter accepts the supplied integer, including negative values. Replacing a public field with public get and set does not by itself enforce nonnegative stock. Encapsulation needs a deliberate write policy.
Use a backing field when a write needs explicit logic
The explicit Stock property separates the public member Stock from the private backing field stock. The getter returns the field. The setter receives value and can inspect it before storing anything. Using different capitalization helps show the two different members; the field and property are not interchangeable names for one declaration.
To require nonnegative values, reject a negative value before assigning stock. If you assign first and validate afterward, an exception could leave the object holding invalid state. The example starts at five, attempts minus one and then reads five again after handling the rejection.
A setter does not return a Boolean success result. Here invalid assignment throws an argument exception, caught in Main for explanation. For a domain action that commonly refuses a request, a method such as TrySell can communicate acceptance with a Boolean instead. Avoid a setter that silently ignores invalid input and leaves callers guessing whether assignment worked.
Program.cs · A backing field makes each accessor visible
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
5
Stock is the property and stock is its private backing field. In set, value is the integer supplied by the assignment. In get, return stock supplies the current value to the caller. This explicit version still has no validation; it first shows where such logic belongs.
Program.cs · Validate before writing so rejected assignments preserve state
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Rejected negative stock
Still stored: 5
The setter checks the incoming value before assigning the backing field. A rejected negative assignment throws and preserves five. The catch handles only this expected demonstration error. A setter has no ordinary return value; TrySell below uses a method and Boolean when refusal is a normal operation result.
A public getter does not require a public setter
public int Stock { get; private set; } lets callers observe the count but restricts assignment to the declaring class. Its constructor and methods can still assign Stock. This preserves simple reading syntax while guiding callers toward meaningful operations for state changes.
A private setter is an accessibility boundary, not a validation routine. The constructor must reject invalid opening stock, and TrySell must reject nonpositive or excessive quantities. The private setter prevents unsupported external writes while the method bodies preserve the invariant internally.
Name uses a getter-only automatic property. Its value is assigned during construction, so the caller can identify the item without renaming it later. Getter-only and private-set properties differ: private set remains writable by the class’s ordinary methods, while this getter-only automatic property is assigned through its initializer or owning constructor.
Program.cs · Let callers read stock while operations own its changes
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Before: 5
Accepted: True
After: 3
Stock has a public getter and a private setter. Main may read it, while the constructor and methods inside InventoryItem may assign it. The private setter does not validate the class’s own writes, so the constructor and TrySell still enforce their rules.
Program.cs · An external assignment cannot use a private setter
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: the Stock set accessor is inaccessible.The property is readable from Main, but its setter is private. Use pen.TrySell(quantity) for the supported stock-decrease operation. Read and write accessibility are deliberately different.
Program.cs · A getter-only name cannot be reassigned by the caller
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: Name cannot be assigned because it is read-only.This getter-only automatic property is assigned by the owning constructor. Main cannot assign it later or through an object initializer. Read-only access to this string is not a general guarantee that every object reachable through any getter-only property is deeply immutable.
Compute derived facts instead of duplicating state
InStock answers whether Stock is greater than zero. Storing both a count and an independent availability flag would create two values that could disagree. A computed getter derives the Boolean from the current count whenever a caller reads it.
The displayed getter body returns Stock > 0 directly and does not change the object. The shorter expression-bodied form public bool InStock => Stock > 0 expresses the same simple calculation. Neither form creates a separate stored availability flag in this example.
Keep ordinary property reads quick and predictable. Reading InStock should not sell an item, increment analytics, save a row or perform a network request. More substantial operations usually deserve a method name that makes their work visible. A computed getter can return a different value later because underlying state changed, not because the read itself changed state.
Program.cs · Compute a fact from current state instead of storing a duplicate
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
True
False
InStock computes Stock > 0 each time it is read and has no separate Boolean backing field. Selling the final units changes Stock, so the next read immediately reports false. There is no cached flag to forget to update. This getter only observes state.
Init restricts assignment timing, not value meaning
An init accessor supports initialization-time assignment, including an object initializer, while rejecting ordinary assignments afterward. This is useful for options that callers choose as they construct an object. It is not the same policy as private set, which is available to later methods inside the declaring class.
LabelOptions demonstrates the timing rule without introducing more advanced construction features. The first program configures Prefix in its initializer. The second attempts to replace Prefix after that initializer and deliberately fails compilation. The private-set and getter-only examples demonstrate different permitted write contexts.
Neither init nor a getter-only declaration automatically validates a name, enforces a required input, or makes referenced child objects deeply immutable. C# also has required properties and newer field-backed syntax, but this lesson uses explicit backing fields and constructors so each rule remains visible. Choose a write policy from the model’s needs before choosing the shortest syntax.
Program.cs · Init permits configuration during object initialization
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stock
init allows assignment during initialization, including this object initializer, while preventing ordinary later assignments. It is different from private set, which the owning class can continue using in its methods. This simple string option has no custom validation.
Program.cs · Init does not allow ordinary later reassignment
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: the init-only property can only be assigned in allowed initialization contexts.Once the object initializer has completed, this ordinary assignment is invalid. init controls when an assignment is allowed; it does not automatically require a nonempty value or validate its meaning.
Encapsulation must cover every supported write path
Encapsulation combines an intentional interface with implementation rules that preserve valid state. A private backing field plus a validated setter is only safe when constructors and other methods also respect the same rule. Writing the field directly bypasses the property’s accessor code.
The guided repair shows a constructor storing a negative field even though the public setter rejects negative assignments. Routing construction through the setter reuses that check. Another valid design is a shared private validation helper used by both constructor and accessor, especially when diagnostic parameter names or different failure policies matter.
Our inventory lab uses a different sensible policy: callers cannot assign Stock at all after creation. They read it publicly and request a decrease through TrySell. The constructor validates starting state, the operation guards later changes, and InStock is computed. You do not need public setters for every stored fact just because properties support them.
Test values, refusal behavior and access boundaries
Test a validated setter with zero, a positive count and a negative assignment. Verify the stored value after rejection rather than checking only the exception message. For construction, test blank names and negative stock so invalid initial state does not bypass later property rules.
For the inventory operation, sell all remaining units and check that Stock becomes zero and InStock becomes false. A subsequent sale must be refused without changing either result. Repeat property reads to ensure reporting itself has no side effects.
Access is also observable behavior. The lab starter and solution intentionally print the same report, but the solution must reject external Stock assignment. Intentional compiler failures demonstrate private setters, getter-only assignment and init-only timing. These are separate from runtime input validation and from persistence or multi-user inventory coordination, which this in-memory exercise does not implement.
Guided repair: include initialization in the validation rule
Stock rejects negative assignments, but new StockEntry(-1) still creates an invalid count. The constructor writes the backing field directly. Make initialization respect the same property rule.
Program.cs · A constructor can bypass a validated property
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
-1
The constructor assigns the backing field directly, bypassing the setter’s nonnegative check. A validated property does not magically guard all writes to its backing field. Every initialization and mutation path must preserve the invariant.
Repair checkpoints
- Find stock = initialStock in the constructor.
- Assign Stock = initialStock so the setter validates before storage.
- Keep the negative-value check before stock = value in the setter.
- Verify negative construction is rejected rather than printing -1.
- Recognize that larger designs may share a private validator instead of duplicating rules.
Reveal the repair and explanation
Program.cs · Route constructor assignment through the same validation
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Rejected invalid construction
The constructor now assigns Stock, so the validated setter runs before storage. Negative construction fails instead of returning an invalid object. Because validation belongs to the setter here, its diagnostic names value; a reusable private validation helper could preserve constructor-specific parameter naming in a larger design.
Independent lab: readable stock with guarded changes
Close the inventory item’s unrestricted property write path while preserving readable Name, Stock and computed InStock values. Keep changes flowing through valid construction and the guarded sale operation.
Acceptance checklist
- Keep Name as a getter-only automatic property assigned by the validated constructor.
- Change Stock to a public getter with a private setter.
- Keep InStock computed from Stock > 0 rather than a separately stored Boolean.
- Preserve name trimming and rejection of blank names or negative opening stock.
- Keep TrySell refusing nonpositive and excessive quantities before updating Stock.
- Preserve the reference output, including availability changing to False after selling the last units.
- Verify external Stock assignment fails compilation while permitted reads remain available.
Program.cs · Inventory property-boundary starter
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 2, available: True
Sale: True
Oversell: False
Pen: 0, available: False
The demonstrated workflow gives correct output, but public set still lets a caller assign arbitrary stock. Review the contract as well as the sample run. Close that unsupported write path while preserving the read-only report and guarded operation.
Show hints
- The lab needs a setter access change rather than a new stock formula.
- Methods inside InventoryItem can still use a private setter.
- Do not add an availability flag that must be synchronized manually.
Reveal the reference solution
Program.cs · Inventory property-boundary reference solution
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 2, available: True
Sale: True
Oversell: False
Pen: 0, available: False
Stock now has a private setter. The constructor validates opening state, TrySell validates later decreases, and InStock derives its answer from the current count. The normal report matches the starter, while an external Stock assignment no longer compiles. No inventory is saved or synchronized by this program.
Changed-input checks
- An item with zero stock reports InStock False and refuses a sale of one.
- A sale of all remaining units succeeds and changes InStock to False.
- Zero, negative and excessive sale quantities leave Stock unchanged.
- An external pen.Stock = -1 must fail compilation.
- A rejected negative assignment in the separate validated-setter example preserves the previous value.
- Repeated Stock and InStock reads do not mutate the object.
Recap: read policy and write policy
Choose how values are read, where assignment is allowed and which operation preserves the invariant.
get- Return or compute the value requested by a read.
set- Receive an assignment through value and decide how to store it.
automatic property- Use compiler-generated backing storage without custom accessor bodies.
private backing field- Explicit internal storage behind a property.
get; private set;- Allow public reading while the declaring class owns later writes.
getter-only automatic property- Assign through its initializer or owning constructor, with no later ordinary setter.
computed getter- Derive an answer from current state without a duplicate stored flag.
init- Permit initialization-time assignment, not ordinary reassignment afterward.
Frequently asked questions
Does a property automatically validate its value?
No. Automatic get and set provide storage access without your application rules. Add explicit validation or restrict the write path.
What is value inside a setter?
It is the value supplied by the caller’s assignment, with the property’s declared type. It is not the existing backing field.
Why use private set rather than no setter?
A private setter lets the declaring class’s methods update the property later. A getter-only automatic property is assigned through its initializer or owning constructor.
Does a computed getter need a backing field?
Not necessarily. InStock derives its Boolean from Stock on each read, avoiding a separate flag that could become stale.
Does init guarantee valid input?
No. It restricts assignment timing. Value validation and required input rules are separate concerns.
Can a constructor bypass setter validation?
Yes, by assigning the backing field directly. Reuse the setter or a shared validator when the same invariant must hold during initialization.
Can you explain each property’s contract?
Trace getters, incoming values, private setters, computed facts and validation. Read the explanation after checking your answers.
Next step and references
Properties expose accessor behavior through convenient member syntax. Validation must run before storage and cover initialization as well as later writes. Use computed facts and guarded operations to avoid inconsistent state.
Next is C# Inheritance and the Base Class. You will model a genuine is-a relationship, initialize the base part of an object and compare inheritance with composition.
Official references
Original SovranCode explanations, examples and exercises. Technical details were checked against these references.
Last updated: .