C# Object-Oriented Thinking: State, Behavior, and Responsibility
An object connects what it remembers with the operations it can perform. Build a simple mental model of classes, objects and responsibilities before adding more syntax or larger designs.
What you will understand
- Describe an object’s responsibility in one clear sentence.
- Distinguish a class definition from one object instance.
- Identify state, behavior and the caller’s coordination role.
- Explain separate instances versus two references to one instance.
- Trace accepted and refused borrowing transitions.
- Keep a state rule behind a private field and public operations.
- Recognize accidental static state in an instance model.
- Test both operation results and resulting state.
Start with a responsibility, not a list of objects
Object-oriented programming groups related state and behavior into objects. State is what an object currently knows or remembers. Behavior is what it can do, including operations that change that state according to a rule. The goal is to make a program easier to reason about, not to turn every noun into a class.
Our example models the borrowing status of one physical library copy. Its state answers whether that copy is borrowed. Its behavior accepts a borrow request only while available and accepts a return only while borrowed. We leave readers, due dates, fines and a database out of this first model so its responsibility stays small.
A short script with one title and one Boolean can be perfectly reasonable. A class becomes useful when several copies need the same rules and each copy needs its own state. Before reading the code, write the sentence: this object represents one copy and controls its borrowing status. That sentence helps decide which data and operations belong together.
Program.cs · Start with the problem before choosing a class
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River Maps: False
One title and one Boolean are easy to follow in a tiny script. The Boolean means whether this particular copy is borrowed. A class becomes useful when that state must stay connected to the operations and rules for that copy; it is not required just to print two values.
A class is a definition; an object is one instance
A class defines a custom type. It describes members that its objects can have, such as stored fields and methods. Declaring class BookLoan does not create a borrowed copy by itself. new BookLoan() creates an object using that definition, and a variable of class type holds a reference to the object.
In the first example, Title is a string field and Borrowed is a Boolean field. loan.Title uses dot notation to select the Title member on the object referenced by loan. The initial values are written in the class so the example starts with a visible title and an available copy.
The public fields are a short stepping stone, not the final design. They let a caller write state freely. We will shortly keep the borrowed flag private and expose meaningful operations. Constructors and properties are introduced in later lessons; for now, focus on the difference between defining a type and creating an instance. Each editor contains its own complete program.
Program.cs · A class describes; new creates an instance
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River Maps: False
class BookLoan declares a type with two fields. new BookLoan() creates an instance, and loan holds a reference to it. The dot selects a member on that object. public fields make this first example easy to inspect; later examples protect the borrowed state rather than allowing arbitrary assignments.
Separate objects have separate instance state
Two new BookLoan() expressions create two distinct objects. They use the same class definition, but each has its own instance fields. Borrowing one physical copy should not mark a different copy as borrowed. Even two copies with equal titles can still be different objects with different availability.
A variable name is not an object identity. BookLoan same = first creates another reference to the existing object; it does not create a second object. A change through same is therefore visible through first. Think of two labels pointing to one box rather than two boxes holding duplicated contents.
This reference behavior follows the array lesson: copying a reference does not clone the referenced data. The example uses object.ReferenceEquals to check identity directly. A real catalog might also assign stable copy identifiers, but a title or a local variable name is not a substitute for that identifier.
Program.cs · Two copies can hold independent state
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River Maps: True
Night Trains: False
There are two new expressions and two distinct objects. Changing the first object’s instance field does not change the second object’s field. Each modeled object represents one physical copy, not every book with a matching title.
Program.cs · A second variable is not necessarily a second object
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
True
True
same = first copies a reference, so both variables reach one object. The mutation is visible through first. ReferenceEquals asks whether the references identify the same object; it does not compare book titles. This is the same reference-copy idea you met with arrays.
Put a rule beside the state it controls
An instance method operates on a particular receiving object. In loan.TryBorrow(), loan identifies that receiver. Inside the method, borrowed refers to that object’s field. Two BookLoan objects can use exactly the same method definition while getting different answers from their own current states.
TryBorrow first checks whether the copy is already borrowed. If it is, the method returns false without a change. Otherwise it sets borrowed to true and returns true. Read that result as request accepted, not as the current state. The separate IsBorrowed method answers the state question.
The word Try is a naming convention that signals an operation may refuse an ordinary request and report success with a Boolean. It is not a special keyword. We use it to make the contract understandable at the call site. Do not use the returned false as an instruction to reset the flag: a refused duplicate borrow must leave the copy borrowed.
Program.cs · Ask the object to perform a meaningful operation
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Accepted: True
Borrowed: True
TryBorrow checks and changes the borrowed field of the receiving loan. It returns whether this request was accepted. IsBorrowed reports current state without changing it. These are instance methods: loan.TryBorrow() acts on loan, rather than on a shared global loan.
Program.cs · A refused request leaves state unchanged
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First request: True
Second request: False
Still borrowed: True
The first call changes available to borrowed and returns true. The second finds it already borrowed, returns false and performs no state change. A false operation result means refusal, not that the object is now available. Keep operation success separate from a state query.
Protect the transition instead of exposing a flag
Encapsulation means presenting a useful boundary around related data and behavior. Here the caller asks for TryBorrow or TryReturn rather than assigning the availability flag directly. The private borrowed field can be accessed by BookLoan’s implementation, while the public methods are the supported operations used by Main.
The state diagram has two states: available → borrowed through an accepted borrow, and borrowed → available through an accepted return. Borrowing an already borrowed copy or returning an available copy is refused and leaves state unchanged. A good model explains these invalid transitions as clearly as the successful ones.
Private access makes accidental external writes a compiler error, as one intentional example demonstrates. It does not provide user authentication, durable storage or atomic protection between concurrent web requests. Title remains a simple public field in this introduction; the protected rule is specifically the borrowing flag. Later lessons build stronger initialization and property boundaries.
Program.cs · Model both directions of the state change
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Return before borrowing: False
Borrow: True
Return: True
Borrowed afterward: False
An available copy cannot be returned under this exercise’s rule, so the first request is refused. After borrowing, returning is accepted and restores availability. The object owns the check and change together; the caller does not directly assign the private flag.
Program.cs · A private field cannot be assigned 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: BookLoan.borrowed is inaccessible due to its protection level.private limits field access to BookLoan’s implementation. Main must use the public operation. This intentionally fails compilation. Access control helps maintain an API boundary; it is not encryption, user authorization or a database lock.
Let the model enforce rules and the caller present results
Main coordinates the small program: it creates objects, sends requests and prints a report. BookLoan manages its own borrowing rule and answers a state query. Keeping Console.WriteLine outside the class lets the same operation be used by a different presentation without rewriting its rule.
A report can inspect an array of BookLoan references and print each object’s current status. The array groups the objects; it does not turn their fields into shared state. Formatting the report should not borrow or return anything as a hidden side effect.
You do not need inheritance or a framework to start thinking in objects. Abstraction here means exposing the useful question and operations while keeping the flag’s representation behind the boundary. Inheritance and polymorphism are later tools for suitable relationships, not requirements for every class. A pure arithmetic helper can also remain a static method when it has no instance state to manage.
Program.cs · Keep the report separate from the model rule
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
River Maps: borrowed
Night Trains: available
The model manages borrowing; Main chooses report text and loops through the objects. The conditional expression selects a word from the state query. An array groups references without merging their states. Printing the report does not need to mutate the loans.
Test the object’s story, including refused actions
Write the expected story before running code: a new copy is available; the first borrow succeeds; a repeated borrow fails; the return succeeds; a repeated return fails. Check both the operation’s Boolean result and the state afterward. A correct message alone cannot prove that the underlying state changed properly.
Then create two independent objects and borrow only one. If both become borrowed, inspect whether a field was accidentally declared static or whether both variables refer to the same object. Those are different causes: shared type state and shared object identity. The repair below deliberately demonstrates the static-field mistake.
This model is intentionally small and temporary. It has no saved reader account, inventory database, due date, network request or thread coordination. Restarting an editor run creates fresh objects. A useful next design question is what extra responsibility you would introduce and where it belongs, rather than adding unrelated fields to BookLoan without a rule.
Guided repair: keep each copy independent
Two distinct new BookLoan objects both report borrowed after borrowing only the first. Find why per-copy state became shared, then fix it without changing static Main or exposing the flag publicly.
Program.cs · A shared flag accidentally joins unrelated copies
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First: True
Second: True
The static field belongs to the type and is shared by these instances. Borrowing first makes second appear borrowed too, even though it is a separate copy. This is a model error: per-copy availability was stored as type-wide state.
Repair checkpoints
- Confirm first and second are created by separate new expressions.
- Find private static bool borrowed in the model.
- Remove static from the borrowed field so it belongs to each instance.
- Preserve public TryBorrow and IsBorrowed instance methods.
- Verify First: True and Second: False after borrowing first only.
Reveal the repair and explanation
Program.cs · Store availability on each instance
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
First: True
Second: False
Removing static from the field gives each BookLoan its own borrowed state. Both still follow the same method definitions, but those methods read and update their receiver’s instance field. static Main remains the program entry point; it does not make every object’s state shared.
Independent lab: complete the borrowing story
Complete the return behavior for a library copy. Keep borrowing and returning rules inside BookLoan, then use Main to verify the full state-transition story.
Acceptance checklist
- Keep borrowed private and non-static, initially false.
- Keep TryBorrow as the guarded available-to-borrowed transition.
- Implement public bool TryReturn(): return false without a state change if already available.
- Otherwise set borrowed to false and return true.
- Keep IsBorrowed as a state query with no mutation.
- Print the reference sequence: borrow True, duplicate borrow False, return True, duplicate return False, final borrowed False.
- Keep output in Main and preserve independence between separate objects.
Program.cs · Library-copy behavior starter
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Borrow: True
Duplicate borrow: False
Return: False
Duplicate return: False
Borrowed: True
Borrowing already enforces its rule. TryReturn is a compilable placeholder that refuses everything. Implement the missing transition without exposing borrowed publicly or making the field static. The displayed output is the starter’s behavior, not the target result.
Show hints
- Use if (!borrowed) as the refusal guard in TryReturn.
- Only the accepted path writes borrowed = false.
- Test again after a refused operation; its result and current state are different questions.
Reveal the reference solution
Program.cs · Library-copy reference solution
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Borrow: True
Duplicate borrow: False
Return: True
Duplicate return: False
Borrowed: False
TryReturn checks the existing state before changing it. An accepted return resets borrowed; a duplicate return is refused. Both accepted and refused paths return a clear Boolean result. All state is in memory and starts fresh each run; this is not a saved library loan or multi-user reservation.
Changed-input checks
- TryReturn on a fresh object returns False and leaves IsBorrowed False.
- Borrow then return produces True, True and final state False.
- Borrow twice produces True, False and final state True.
- After a successful return, borrowing again succeeds.
- Borrowing the first of two separate objects leaves the second available.
- A second reference to the same object sees the same borrowed state.
Recap: state, behavior and boundaries
Describe what one object represents, the state it owns and the operations that preserve its rules. Distinguish operation success from current state.
class- Defines a custom type and its members.
object / instance- One concrete instance created from a class definition.
reference- A value that identifies an object; assignment does not clone that object.
state- Information remembered by an object, such as its borrowed flag.
behavior- An operation or query associated with that object.
responsibility- The small purpose and rules the model is meant to own.
encapsulation- Expose meaningful operations and control access to the data they maintain.
static versus instance- Type-level state is shared; instance fields belong to individual objects.
Frequently asked questions
Is a class the same as an object?
No. The class defines a type; an object is one instance created from that definition. A variable of class type holds a reference to an object.
Does assigning first to second copy the whole object?
No. For these class variables it copies the reference. Use a separate new expression for a distinct object; copying existing state is a separate design decision.
Why return false for a duplicate borrow?
The copy is already borrowed, so the request is refused under the exercise rule. Its state remains borrowed. A false operation result does not mean the state flag becomes false.
Why is borrowed private but Title public here?
The introduction protects the borrowing transition while keeping title syntax simple. It does not claim to enforce title validity or identity. Later lessons cover constructors, properties and access modifiers in depth.
Do I need inheritance to write an object-oriented program?
No. Objects with clear state, behavior and boundaries can be useful without inheritance. Introduce inheritance only when the modeled relationship and behavior justify it.
Will these objects remain after I refresh or rerun?
No. Each program creates temporary in-memory objects. Persistence, multi-user access and concurrency need additional architecture and are not implemented by this exercise.
Can you explain the object’s responsibility?
Distinguish class definitions, instances, references and state transitions. Read the explanation after checking your answers.
Next step and references
An object connects state with meaningful behavior. Give it a focused responsibility, distinguish separate instances from shared references, and verify both accepted and refused operations.
Next is C# Classes and Objects. You will practice class declarations, instance creation and member access in more detail, then trace independent objects and shared references.
Official references
Original SovranCode explanations, examples and exercises. Technical details were checked against these references.
Last updated: .