Methods

A method is a named block of code with parameters and a return type (void if nothing is returned).

Concept

A method is a named block of code with parameters and a return type (void if nothing is returned). Methods belong to types; in top-level programs you can also write local functions.

Expression-bodied methods: static int Square(int x) => x * x;

Static methods belong to the type (Math.Max); instance methods operate on an object's state (list.Add).

Recursive methods call themselves; each call uses stack space, so very deep recursion can overflow the stack.

Optional Parameters and Named Arguments Parameters with default values (int address = 0x7E0) are optional. Callers can name arguments (extended: true) to skip optional parameters and to make calls self-documenting.

Several methods can share a name if their parameter lists differ (number or types). The compiler picks the best match at compile time. Return type alone does not distinguish overloads, and local functions cannot be overloaded — declare overloads as members of a class.

Pass a copy of the value (for references: a copy of the reference)

Pass the variable by reference; callee may read and write Callee must assign it; caller need not initialize F(out var x)

Read-only by reference (avoids copying big structs) F(in x) or F(x) ref readonly

Read-only by reference, stricter than in about what callers pass

Variable number of arguments as an array/collection Methods can also return by reference (ref / ref readonly returns) to avoid copying large structs.

static double Average(int[] values) { ... return result; }

Why It Matters

Methods are the unit of reuse and testing. Clear signatures — good names, few parameters, out/Try patterns, optional parameters instead of many overloads — make APIs easy to use correctly.

Syntax

[modifiers] ReturnType Name([modifier] Type param = default, ...) { body }
static bool TryParse(string s, out int value) { ... }
static int Sum(params int[] numbers) => numbers.Sum();

Example

// Method declaration, parameters and return values
static double Average(int[] values)
{
    if (values.Length == 0) return 0;
    int sum = 0;
    foreach (int v in values) sum += v;
    return (double)sum / values.Length;
}
Console.WriteLine($"Average = {Average([4, 8, 15, 16, 23, 42])}");

// Optional parameters and named arguments
static string Describe(string ecu, int address = 0x7E0, bool extended = false) =>
    $"{ecu} @ 0x{address:X3}{(extended ? " (29-bit)" : "")}";
Console.WriteLine(Describe("VCU"));
Console.WriteLine(Describe("BMS", 0x7E4));
Console.WriteLine(Describe("GW", extended: true));          // skip 'address' by naming

// Overloading: same name, different parameter lists (overloads must be members of a type)
Console.WriteLine($"Scale(3) = {Units.Scale(3)}, Scale(2.5) = {Units.Scale(2.5)}, Scale(\"ab\") = {Units.Scale("ab")}");

// Recursion
static long Factorial(int n) => n <= 1 ? 1 : n * Factorial(n - 1);
Console.WriteLine($"20! = {Factorial(20)}");

// Parameter passing: value, ref, out, in, params
static void ByValue(int x) => x++;
static void ByRef(ref int x) => x++;
static bool TryDivide(int a, int b, out int quotient)
{
    if (b == 0) { quotient = 0; r
…

Expected Result

ByValue changed only its own copy; ByRef changed the caller's variable.

Average = 18
VCU @ 0x7E0
BMS @ 0x7E4
GW @ 0x7E0 (29-bit)
Scale(3) = 30, Scale(2.5) = 25, Scale("ab") = abab
20! = 2432902008176640000
after ByValue: 5
after ByRef: 6
TryDivide(17, 5): True -> 3; TryDivide(1, 0): False
Distance = 13
Sum() = 0, Sum(1, 2, 3) = 6
peak.Z = 60
odometer 150.5 km, Odometer.ToMiles(100) = 62.1

Common Mistakes

out parameters must be assigned and ref arguments must be initialized — real compiler errors:

Overloading local functions is not allowed:

Too many parameters (more than ~4) — introduce a parameter object or record.

Using ref when a return value would do.

Changing a default parameter value in a public library (callers compiled against the old default keep it).

Unbounded recursion (StackOverflowException terminates the process and cannot be caught).

int value;
Update(ref value);        // ref requires a definitely assigned variable
Console.WriteLine(value);

static void Read(out int result)
{
    Console.WriteLine("forgot to assign result");
}

static void Update(ref int v) => v++;
m12_err.cs(5,13): error CS0177: The out parameter 'result' must be assigned to before control leaves the current method
m12_err.cs(2,12): error CS0165: Use of unassigned local variable 'value'
static int Scale(int x) => x * 10;
static double Scale(double x) => x * 10;
Console.WriteLine(Scale(3));
m12_err2.cs(2,15): error CS0128: A local variable or function named 'Scale' is already defined in this scope

Best Practices

Name methods with verbs (CalculateChecksum) and keep them short and single-purpose.

Use the TryXxx(…, out T result) pattern for operations that commonly fail.

Prefer returning tuples or records to multiple out parameters (Module 26).

Validate arguments at public boundaries (ArgumentNullException.ThrowIfNull(x)).

Interview Relevance

High: "ref vs out", "in parameters", "value vs reference parameters (passing a reference type by value)", "overloading vs overriding", "optional parameters pitfalls".

Practice Questions

Write Clamp(int value, int min, int max) and overloads for double.

Write bool TryParseHexByte(string s, out byte value).

Implement recursive and iterative Fibonacci; compare for n = 40.

Write a params method that returns the maximum of any number of integers.

Show that passing a List<int> by value still lets the method add elements, but assigning a new list inside does not affect the caller.

Quick Quiz

Q1. (Multiple choice) Which modifier requires the method to assign the parameter? A) ref B) out C) in D) params Answer: B.

Q2. (Predict the output) static void Inc(int x) => x++; int n = 1; Inc(n); Console.WriteLine(n);

Answer: 1 — the method incremented a copy (verified in the example).

Q3. (True/False) Two overloads may differ only by return type.

Answer: False. Overloads must differ in their parameter lists.

Q4. (Find the bug) Describe("GW", true); for Describe(string ecu, int address = 0x7E0, bool extended = false).

Answer: true cannot convert to the int address parameter; use the named argument Describe("GW", extended: true).

Coding Challenge

Write static bool TryParseCanFrame(string text, out uint id, out byte[] data) for inputs like "18FEF100#0102030405060708"; it must return false (not throw) for malformed input.

using System.Globalization;

static bool TryParseCanFrame(string text, out uint id, out byte[] data)
{
    id = 0;
    data = [];
    string[] parts = text.Split('#');
    if (parts.Length != 2 || parts[1].Length % 2 != 0 || parts[1].Length > 16) return false;
    if (!uint.TryParse(parts[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out id)) return false;
    try { data = Convert.FromHexString(parts[1]); }
    catch (FormatException) { return false; }
    return true;
}

Console.WriteLine(TryParseCanFrame("18FEF100#0102030405060708", out uint id, out byte[] data)
    ? $"0x{id:X8} [{BitConverter.ToString(data)}]" : "invalid");
Console.WriteLine(TryParseCanFrame("18FEF100#XYZ", out _, out _) ? "valid" : "invalid");