C# Fields, Methods, and Static Members: Know Where State Belongs
Some values belong to one object, some are shared by the type, and others exist only during a method call. Learn to choose the right place for each value and make state changes predictable.
What you will understand
- Distinguish fields, parameters and local variables by their declarations.
- Explain why a local resets while a field remembers state between calls.
- Use an instance method to update only its receiver.
- Read this.field when a parameter hides a field name.
- Separate per-item stock from a shared accepted-operation count.
- Call static members through their type name.
- Explain why a static method needs an explicit object to use instance state.
- Test refused operations, remembered state and shared accounting.
Class members describe stored data and useful operations
A field is a variable declared directly inside a class, outside its methods. A method is an operation declared in the class. Both are members. Our InventoryItem keeps its current stock in a private field and exposes methods to inspect or change that count. The Name field remains public only to keep naming syntax familiar from the previous lesson.
Choose fields for information the object needs to remember between operations. Choose parameters for information supplied to one call, such as the requested sale quantity. Choose local variables for temporary calculations inside that call. Moving every temporary value into a field makes unrelated operations depend on state they do not need.
The inventory examples deliberately start each item with five units so the lesson can focus on ownership and lifetime. Constructors in the next lesson will accept useful starting inputs. These fixed defaults and the public Name field are teaching choices, not a complete production inventory model.
Program.cs · A field remembers state across calls
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
5
3
stock is declared directly inside InventoryItem, so it is a field. It belongs to this object and retains the updated value between calls. The public query returns the count, while the private field prevents Main from assigning an arbitrary count directly.
A local belongs to one invocation; a field can outlive it
A local declared inside a method is working data for that invocation. MakeLine initializes lineNumber to zero every time it is called. Calling the method again starts that declaration again, so incrementing it once returns one again. It does not remember a previous invocation’s local value.
An instance field instead remains part of its object while that object is available. TrySell changes stock, and a later GetStock call reads the updated count from the same receiver. Neither method needs to store the previous local variables of the other method to do that.
Fields receive default initialization, such as zero for int, before explicit field initialization and construction finish. Local variables must be definitely assigned before they are read. Do not confuse these rules: int count inside Main cannot be printed before assignment merely because an int field begins at zero. The intentional compiler example makes that difference visible.
Program.cs · A local is working data for one invocation
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Line 1
Line 1
lineNumber is declared inside MakeLine. Each invocation initializes its own local to zero, then returns Line 1. The local does not remember the previous call just because its name is unchanged. This behavior is appropriate for temporary working data, not a persistent sequence.
Program.cs · Fields have defaults; locals need assignment before reading
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
0
An int field without an explicit initializer starts at zero. This differs from an unassigned local variable. A default value is a language rule, not proof that zero is a valid starting value for every domain. Counter.Read is a query and does not change the field.
Program.cs · A local cannot be read before definite assignment
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: use of unassigned local variable count.The local count does not receive an automatic readable zero merely because an int field would. Assign a meaningful value before reading it. This example deliberately fails compilation to distinguish local definite-assignment rules from field defaults.
Instance members need a particular receiver
An instance field belongs to one object. An instance method has a current receiver and can read its fields. In pen.TrySell(2), pen identifies the item whose stock is checked and changed. The notebook object has separate storage even though its methods come from the same class definition.
The keyword this refers to the current receiver inside an instance method. When a parameter and field have the same name, this.value identifies the field while value identifies the parameter. Use naming that keeps this distinction clear. An assignment to the parameter alone would not store the new value in the field.
static Main can create objects and call their instance methods. It simply has no implicit Program instance of its own. You do not need to make an item’s fields static to access them from Main; create or obtain an item reference and send the request to that receiver.
Program.cs · Instance methods use the current object’s fields
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 3 in stock
Notebook: 5 in stock
The receiver pen supplies the instance whose stock TrySell changes. notebook owns a different stock field, so it remains five. static Main is only the entry point; it can create objects and invoke their instance methods without making their fields static.
Program.cs · Use this to identify the field when a parameter has the same name
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
7
Inside SetForPractice, the parameter named value hides the unqualified field name. this.value identifies the receiving object’s field; value on the right is the parameter. This setter is only a syntax example with no validation, not a recommended inventory operation. this is available in instance methods, not static ones.
A static field belongs to the type rather than each item
A static field is shared at the type level for this ordinary non-generic class in the running program. Here successfulSales intentionally counts accepted operations across items, while stock remains an instance field. One design contains both kinds of state because the two values answer different questions.
Call InventoryItem.GetSuccessfulSales() through the type name. Selling two pens and one notebook is two accepted operations, not three operations: quantities and success counts are different measures. State exactly what a shared metric counts before choosing where to update it.
The refusal guard must run before incrementing the success counter. A zero, negative or excessive quantity does not count as an accepted sale. Test both item stock and the shared count after a refused call. A static field also is not automatically persistent or safe for concurrent writes; the example is a single-threaded, single-process demonstration that starts fresh on each run.
0
2
Pen: 3 in stock
Notebook: 4 in stock
stock is per item, while successfulSales belongs to the type in this program. Two accepted calls increment the shared counter twice, even though three units were sold. The counter measures accepted sale operations, not units, distinct buyers or current inventory. The static query is called through InventoryItem.
Program.cs · A rejected operation must not increment the shared count
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
False
False
0
5
Both requests return before changing either field. The instance stock remains five and the shared success count remains zero. Updating the counter before the guard would record refused attempts as successes. Counter placement is part of the operation’s contract.
Static methods have no implicit object receiver
A static method belongs to the type and has no this reference. It can use parameters, locals and accessible static members. It cannot directly read an instance field without identifying which object supplies that field. The compiler refuses a static ReadStock body that simply returns a non-static stock field.
A pure utility such as multiplying boxes by units per box needs only explicit inputs, so it can be a static method. A static class can group such operations and cannot be instantiated. static does not mean faster, immutable, private, validated or automatically free of side effects; it says the member is associated with the type.
A static method can still work with an explicitly supplied object reference, just like any other reference value. The missing ingredient in the failing ReadStock example is a particular receiver. Decide whether the operation conceptually belongs on an item before changing modifiers simply to satisfy a diagnostic.
Program.cs · A static utility can work entirely from explicit inputs
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
12
StockMath has no instance state to remember. Its static method uses only supplied inputs, so a call needs no StockMath object. A static class cannot be instantiated. The example assumes small nonnegative integers whose product fits int; no inventory is modified.
Program.cs · A static method has no implicit current item
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: an object reference is required for the non-static stock field.ReadStock is static but stock belongs to an instance. There is no current item whose count can be read. Keep a per-item query as an instance method, or pass an explicit object to a static operation when that design is appropriate. Making stock static would change its meaning.
Program.cs · Access a static member through its type name
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: GetSuccessfulSales cannot be accessed with an instance reference.C# requires the static call InventoryItem.GetSuccessfulSales(). The counter is not owned by pen. This is a separate error from calling an instance method through a type name: check both the member declaration and the receiver syntax.
Separate ownership from visibility and presentation
public and private control where source code may access a member; static versus instance determines how it belongs to the type or an object. These are separate choices. Our stock is private instance state, successfulSales is private static state, and public methods provide deliberate ways to inspect them.
Describe returns text for one item while Main decides when to print it. The calculation and mutation remain in the item operation. A query should not accidentally count as a sale or reduce stock simply because the report was displayed twice. Keep read-only reporting separate from successful state changes.
Shared mutable state deserves a clear purpose. Do not make stock static merely to access it from a static method, and do not turn temporary arithmetic into fields to avoid passing parameters. In a real multi-process service, inventory and aggregate counts need authoritative storage and concurrency rules; a type-level field is not a replacement for that architecture.
Trace each variable’s scope and lifetime
For each variable, identify where it is declared: directly in the class, with static in the class, in a parameter list, or inside a method body. Then ask what value it should remember and for how long. This small inspection catches many bugs before execution.
The repair demonstrates a local named stock hiding the intended field update. The guard reads this.stock, but subtraction changes a temporary local. The output looks like successful work even though the object has not remembered the sale. Verify the count afterward instead of trusting the success message alone.
In the lab, test accepted calls, refusal boundaries and two different receivers. Repeat a query to ensure it has no side effect. Confirm the shared counter counts only accepted operations and starts from zero in a fresh program run. Do not infer durable behavior from values remembered within one run.
Guided repair: update remembered state
Two sale requests of two and four units both report success, but stock stays five. The guard reads the field while subtraction changes a local named stock. Restore one remembered per-item count.
Program.cs · A local accidentally replaces remembered stock
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
True
True
5
The guard reads the field through this.stock, but the method then declares a local stock and subtracts from that local. The object’s count stays five, so a second sale of four is incorrectly accepted. A local with the same name does not update the field automatically.
Repair checkpoints
- Locate the local int stock = 5 inside TrySell.
- Remove that declaration so the subtraction updates the instance field.
- Keep the nonpositive and excessive-quantity guard.
- Expect True, False, then remaining stock 3 for the two requests.
- Do not solve the bug by making stock static or resetting it before each sale.
Reveal the repair and explanation
Program.cs · Update the instance field rather than a temporary local
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
True
False
3
Remove the unnecessary local declaration so stock -= quantity updates the field. The first sale leaves three, making the next request of four invalid. The query reads the remembered count after both calls. The fix preserves per-item state rather than moving stock to a shared field.
Independent lab: independent stock and a shared success count
Complete a shared accepted-sale counter while keeping each item’s stock independent. Read the complete model, then add success accounting at the correct point in TrySell.
Acceptance checklist
- Keep stock private and non-static, initially five for each item.
- Keep successfulSales private and static, initially zero in a fresh run.
- Increment successfulSales exactly once for each accepted TrySell call.
- Do not increment it for zero, negative or excessive quantity.
- Keep GetSuccessfulSales static and call it through InventoryItem.
- Preserve item Describe output and print only from Main.
- For the reference sequence, expect pen stock 3, notebook stock 4 and Accepted sales: 2.
Program.cs · Per-item stock and shared reporting starter
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen sale: True
Oversell: False
Notebook sale: True
Pen: 3 in stock
Notebook: 4 in stock
Accepted sales: 0
The stock transitions already work. The shared counter is declared and can be read but is never incremented. Complete only the missing success accounting, keeping the refusal paths unchanged and both stocks independent.
Show hints
- The guard returns immediately on a refused request.
- Put the increment after stock is successfully decreased and before returning true.
- Count accepted operations, not the quantity supplied by the call.
Reveal the reference solution
Program.cs · Per-item stock and shared reporting solution
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen sale: True
Oversell: False
Notebook sale: True
Pen: 3 in stock
Notebook: 4 in stock
Accepted sales: 2
Each accepted sale updates its receiver’s stock and increments the shared counter once. The refused oversell changes neither. This static counter is temporary single-process teaching state: it is not durable analytics, a cross-server total or a thread-safe production transaction.
Changed-input checks
- Selling 0, -1 or 6 from a fresh five-unit item leaves stock 5 and success count 0.
- Selling all five units succeeds once; a later sale of one is refused.
- Selling two units in one request counts as one accepted operation.
- One accepted sale on each of two items counts as two operations while stocks stay independent.
- Calling GetStock, Describe or GetSuccessfulSales repeatedly does not change any counts.
- A fresh standalone program run starts the shared counter at zero.
Recap: scope, ownership and lifetime
Identify each declaration’s scope, whether it belongs to the type or an instance, and whether it should survive the current call.
instance field- State owned by one object and remembered across its method calls.
static field- Type-level state shared in this running program.
parameter- Input supplied for one method invocation.
local variable- Temporary working data declared within a method body.
instance method- An operation with a current object receiver.
static method- A type-level operation with no implicit this receiver.
this.value- Explicitly identify the current object’s field when a parameter hides its name.
public / private- Control accessibility separately from instance or static ownership.
Frequently asked questions
Is a field just another name for a local?
No. A field is declared directly in the class and belongs to an object or the type. A local is declared inside a method and belongs to that invocation.
Does static mean constant?
No. A static field can be mutable. static describes type-level ownership, not whether a value can change.
Why does pen.GetSuccessfulSales() fail?
That method is static. C# requires access through the type name: InventoryItem.GetSuccessfulSales().
Can a static method ever use an object?
Yes, when it has an explicit reference, such as a parameter. It has no implicit current instance or this reference of its own.
Why write this.value = value?
The parameter value hides the field’s unqualified name. this.value selects the current object’s field, while the right-hand value reads the parameter.
Will a static counter survive a restart or synchronize servers?
No. This counter is temporary state in one running process. It is not durable storage, a distributed total or automatic concurrency control.
Can you identify where state belongs?
Distinguish fields, locals, instance receivers and shared static members. Read the explanation after checking your answers.
Next step and references
Fields store remembered state, locals hold temporary work and parameters supply call inputs. Keep per-object data on instances and choose shared static state only for a clear type-level purpose.
Next is C# Constructors and Initialization. You will require useful starting inputs, use this to distinguish fields from parameters and reject invalid starting state.
Official references
Original SovranCode explanations, examples and exercises. Technical details were checked against these references.
Last updated: .