Methods
Methods encapsulate behavior — parameters in, return values out, optional ref/out/in modifiers for advanced scenarios.
Introduction
Methods encapsulate behavior — parameters in, return values out, optional ref/out/in modifiers for advanced scenarios. Well-designed methods follow Single Responsibility: one reason to change, clear naming, and minimal side effects.
C# supports overloading (next lesson), local functions, expression-bodied members, and static methods for pure utilities. Async methods return Task or ValueTask. Interviewers probe pass-by-value vs reference, default parameters, and params arrays.
Enterprise services expose methods on interfaces; DI containers inject implementations. This lesson builds method fundamentals before OOP layers.
The story
When a customer withdraws cash at an ATM, the system must verify the amount is valid and does not exceed the balance — without throwing exceptions for ordinary "insufficient funds" cases. A TryWithdraw method returns false and leaves the balance unchanged when the withdrawal fails, which is exactly how real banking APIs communicate expected failures.
Understanding the topic
Key concepts
- Signature: access modifier, return type, name, parameters.
- Pass by value copies reference or struct bits; ref passes alias.
- out for multiple returns; TryParse pattern.
- Optional parameters with defaults; named arguments at call site.
- params T[] for variable argument lists.
- Local functions capture outer scope — useful for validation helpers.
Step-by-step explanation
- Caller pushes arguments per calling convention.
- Method frame allocated on stack with locals.
- return exits and optionally supplies value.
- void methods for side effects; prefer returning Result types.
- Expression body => for one-liners.
- Static methods cannot access instance members.
Practical code example
Try-style method with out parameter and local function validation:
namespace TechLearningPro.Methods;public static class WithdrawalService{public static bool TryWithdraw(decimal balance, decimal amount, out decimal newBalance){newBalance = balance;if (!IsValidAmount(amount))return false;if (amount > balance)return false;newBalance = balance - amount;return true;static bool IsValidAmount(decimal value) => value is > 0 and <= 1_000_000m;}}
Line-by-line code explanation
TryWithdraw(decimal balance, decimal amount, out decimal newBalance)follows the Try-pattern: bool success plus an output parameter.newBalance = balanceinitializes the out parameter before any early exit.if (!IsValidAmount(amount)) return falserejects zero, negative, or absurdly large amounts.if (amount > balance) return falsehandles insufficient funds without an exception.newBalance = balance - amountcomputes the updated balance only when the withdrawal succeeds.return truesignals success to the caller.static bool IsValidAmount(decimal value)is a local function scoped inside the method.value is > 0 and <= 1_000_000muses a relational pattern for concise range validation.
Key takeaway: Try pattern avoids exceptions for expected failures. static local function cannot capture instance — pure validation.
Real-world use
Where you'll use this in production
- Domain services with Try* methods for business rule failures.
- Utility libraries with static helper methods.
- Repository interfaces with CRUD method contracts.
- Extension methods adding behavior to existing types (later lesson).
Best practices
- Methods under 20 lines; extract when growing.
- Use Try pattern instead of exceptions for control flow.
- Avoid ref unless interop or high-performance APIs.
- Name methods with verbs: CalculateTotal, ValidateInput.
- Document preconditions with ArgumentException throws.
Common mistakes
- Too many parameters — use parameter object record.
- Side effects in methods named Get*.
- Returning null instead of Try or Option pattern.
- public void methods that should return bool success.
Advanced interview questions
Q1BeginnerPass by value in C#?
Q2BeginnerPurpose of out parameter?
Q3Intermediateref vs out?
Q4IntermediateWhen use local functions?
Q5AdvancedDesign method signatures for idempotent payment capture.
Summary
Methods bundle reusable logic with clear signatures. Try/out pattern handles expected failures gracefully. Keep methods small, named with verbs, minimal side effects. Local functions and expression bodies reduce noise. Next: method overloading and compile-time dispatch.