C# Methods
A method is a named block of C# code that performs one job and can be called whenever that job is needed. Methods matter because they turn long programs into reusable, testable pieces: instead of repeating the same calculation or validation logic, you write it once and call it by name.
In C#, almost all executable code lives inside methods. Even Main, the starting point of a console program, is a method.
Overview: How C# Methods Work
A method belongs to a type, usually a class, struct, or record. A method declaration describes the method’s name, return type, parameters, and modifiers such as static. The combination of the method name and parameter list is important because it is how the compiler decides which method you mean when you call it.
When a program calls a method, execution temporarily jumps to that method body. The runtime creates a new stack frame for the call. That stack frame stores the method’s local variables, parameter values, and bookkeeping information needed to return to the caller. When the method finishes, its stack frame is removed and execution continues after the call site.
Methods can return a value with return, or they can return void when their purpose is an action rather than a computed result. A method that returns int, string, bool, or any other non-void type must return a compatible value on every normal path through the method.
The static keyword means the method belongs to the type itself, not to a specific object. In beginner console programs, helper methods are often marked static because Main is static and can call other static methods directly. Later, when you create objects, instance methods will use object state through fields and properties.
Parameters are local variables initialized by the caller. By default, C# passes arguments by value. For value types such as int and bool, the method receives a copy of the value. For reference types such as string, arrays, and lists, the reference is copied, so the method can use the same object, but assigning the parameter to a different object does not change the caller’s variable.
Syntax
access_modifier static_or_instance return_type MethodName(parameter_type parameterName)
{
statements;
return value;
}
| Part | Meaning |
|---|---|
access_modifier |
Controls where the method can be called from, such as public or private. |
static_or_instance |
static belongs to the type. Without static, the method is called on an object instance. |
return_type |
The type of value the method returns, or void if it returns no value. |
MethodName |
A descriptive PascalCase identifier, such as CalculateTotal. |
parameter_type parameterName |
Input accepted by the method. Multiple parameters are separated with commas. |
return value |
Sends a result back to the caller and immediately exits the method. |
A method can have no parameters, one parameter, or many parameters. It can also use optional parameters, named arguments, overloads, and modifiers such as out or ref, but the basic shape is always a signature followed by a body.
Examples
Calling Simple Methods
using System;
class Program
{
static void Main()
{
SayHello("Ada");
int squared = Square(6);
Console.WriteLine($"6 squared is {squared}");
}
static void SayHello(string name)
{
Console.WriteLine($"Hello, {name}!");
}
static int Square(int number)
{
return number * number;
}
}
Output:
Hello, Ada!
6 squared is 36
SayHello is a void method because it prints a message and does not calculate a value for the caller. Square returns an int, so Main can store the result in squared and use it later.
Returning a Business Calculation
using System;
using System.Globalization;
class Program
{
static void Main()
{
decimal subtotal = 80.00m;
decimal total = CalculateFinalPrice(subtotal, 0.075m, 10.00m);
Console.WriteLine("Subtotal: " + subtotal.ToString("0.00", CultureInfo.InvariantCulture));
Console.WriteLine("Final total: " + total.ToString("0.00", CultureInfo.InvariantCulture));
}
static decimal CalculateFinalPrice(decimal subtotal, decimal taxRate, decimal discount)
{
decimal discounted = subtotal - discount;
decimal tax = discounted * taxRate;
return discounted + tax;
}
}
Output:
Subtotal: 80.00
Final total: 75.25
This example keeps the price formula in one method. The caller does not need to know each intermediate step; it only supplies the subtotal, tax rate, and discount. The method uses decimal, which is preferred for money because it represents base-10 values more predictably than double.
Using out for a Try Method
using System;
class Program
{
static void Main()
{
string input = "42";
if (TryReadAge(input, out int age))
{
Console.WriteLine($"Age next year: {age + 1}");
}
else
{
Console.WriteLine("Invalid age");
}
}
static bool TryReadAge(string text, out int age)
{
bool parsed = int.TryParse(text, out age);
return parsed && age >= 0;
}
}
Output:
Age next year: 43
The TryReadAge method returns bool to say whether parsing succeeded, while the out parameter carries the parsed age. This pattern is common in .NET: int.TryParse, Dictionary.TryGetValue, and many similar methods avoid exceptions for ordinary failed attempts.
Method Overloading
using System;
class Program
{
static void Main()
{
Console.WriteLine(FormatName("Ada"));
Console.WriteLine(FormatName("Grace", "Hopper"));
Console.WriteLine(FormatName("Katherine", "Johnson", "Dr."));
}
static string FormatName(string firstName)
{
return firstName;
}
static string FormatName(string firstName, string lastName)
{
return firstName + " " + lastName;
}
static string FormatName(string firstName, string lastName, string title)
{
return title + " " + firstName + " " + lastName;
}
}
Output:
Ada
Grace Hopper
Dr. Katherine Johnson
These three methods share the same name but have different parameter lists. This is called overloading. The compiler selects the overload by comparing the arguments at the call site to the available signatures.
How Methods Work Step by Step
- The compiler reads each method declaration and records its signature: name, parameter types, return type, and modifiers.
- When the compiler sees a method call, it checks that a matching method exists and that each argument can be converted to the required parameter type.
- At runtime, the CLR enters the method by creating a stack frame for that call. Parameters and local variables live there for the duration of the call.
- If the method calls another method, a second stack frame is created on top of the first. This is why deeply recursive methods can eventually run out of stack space.
- When a
returnstatement runs, the return value is copied back to the caller if there is one. The method’s local variables go out of scope.
Because local variables exist only inside their method call, two methods can use the same local variable name without conflict. This scoping rule keeps methods independent and prevents accidental changes to unrelated code.
Common Mistakes
Forgetting to Return a Value
static int Double(int number)
{
int result = number * 2;
}
This does not compile because the method promises to return an int, but no return statement sends one back. The corrected version returns the computed value:
using System;
class Program
{
static void Main()
{
Console.WriteLine(Double(7));
}
static int Double(int number)
{
int result = number * 2;
return result;
}
}
Output:
14
Expecting a Parameter Assignment to Change the Caller
using System;
class Program
{
static void Main()
{
int count = 5;
Reset(count);
Console.WriteLine(count);
}
static void Reset(int value)
{
value = 0;
}
}
Output:
5
The method changes its local copy of the integer, not the caller’s variable. Usually the clearest fix is to return the new value and assign it explicitly:
using System;
class Program
{
static void Main()
{
int count = 5;
count = Reset();
Console.WriteLine(count);
}
static int Reset()
{
return 0;
}
}
Output:
0
Calling an Instance Method Like a Static Method
class Counter
{
public int GetNext()
{
return 1;
}
}
int value = Counter.GetNext();
GetNext is an instance method, so it must be called on a Counter object. Either create an object or make the method static if it truly does not depend on object state.
Best Practices
- Give methods verb-based names such as
CalculateTax,PrintReport, orTryReadAge. - Keep methods focused on one clear job. If a method validates input, saves data, formats text, and prints output, it is probably doing too much.
- Prefer returning values over changing far-away state. Return values make data flow easier to read and test.
- Use
voidfor actions and a return type for questions or calculations. - Keep parameter lists short. If a method needs many related values, consider grouping them into a class, record, or struct later in the course.
- Use overloads when the operations are genuinely the same idea with different inputs. Do not overload unrelated behavior just to reuse a name.
- Avoid surprising side effects. A method named
GetTotalshould not also delete records or print to the console. - Make helper methods
privatewhen they are only used inside the same class.
Practice Exercises
- Write a method named
IsEventhat accepts anintand returnstruewhen the number is even. - Write a method named
Repeatthat accepts astringand a count, then returns the text repeated that many times. - Write two overloaded methods named
Area: one for a rectangle with width and height, and one for a circle with radius.
Summary
- A method is a named, reusable block of code that belongs to a type.
- A method signature includes its name and parameters; its declaration also includes a return type and modifiers.
voidmethods perform actions, while non-void methods return values.- Arguments are passed by value by default, so assigning a parameter does not replace the caller’s variable.
- The CLR uses stack frames to track each active method call and its local variables.
- Good methods are small, clearly named, predictable, and easy to test.
