Last Updated: May 17, 2026
A method is a named block of code that runs when something calls it. Every C# program is made of methods, starting with the entry point and branching out. This chapter covers what a method actually is, how to declare one, how to call it, the parts that make up its definition, and the rules around where methods live and how they're named. The parameter and return-type details are kept light here; the _Method Parameters_ and _Return Types & Tuples_ lessons take those apart in depth.
Before the syntax, the reason. Take a tiny program that prints the subtotal of three cart items:
The same three lines repeat three times. Variable names like price1, price2, price3 aren't really three different things, they're three instances of the same idea. If the formula has to change tomorrow (add a discount, format differently), you'd edit it in three places and probably miss one.
Now the same logic with a method:
Same result. The formula lives in one place. The name PrintSubtotal describes what's happening. Adding a fourth item is one line, not four.
That captures the three reasons methods exist:
Beyond these three, methods also let you hide complexity. A caller of PrintSubtotal doesn't need to know whether the price is multiplied by quantity, divided by some factor, or run through a discount table. They just say "print the subtotal for these inputs."
A method declaration in C# has a fixed shape. Here is the most common form, with each part labeled:
Reading left to right, the pieces are:
static here). Keywords that affect how the method behaves or who can call it.void here). The kind of value the method gives back to its caller, or void if it returns nothing.PrintSubtotal). The identifier you use to call the method. PascalCase by convention.(int itemNumber, decimal price, int quantity)). The inputs the method accepts. Each parameter has a type and a name, separated by commas. The parentheses are required even when the list is empty.{ ... }). The block of code that runs when the method is called.The same picture as a diagram:
The first three pieces together (static void PrintSubtotal(int itemNumber, decimal price, int quantity)) are called the method signature. The body that follows is the method implementation. The signature is the contract: it tells callers what the method is called, what it expects, and what it returns. The body is how that contract is fulfilled.
Two methods can share the same name if their parameter lists differ. That's called overloading, and it's the topic of the _Method Overloading_ lesson. For now, treat each method as having a unique name in its file.
The most basic method takes no input and returns nothing. It just does something when called.
Three things to notice. First, the method is declared after the call site. Order doesn't matter for top-level methods; the compiler reads the whole file before deciding what's valid. Second, the parentheses at the call site are required even though there are no arguments. PrintWelcome (without ()) is not a call, it's a reference to the method. Third, each call runs the body from start to finish, then returns to the line after the call.
Adding a parameter lets the caller customize what happens:
The parameter name is a local variable inside the method, populated with whatever string the caller passed in. The two calls pass different strings, so the same body produces different output. Parameters open up a whole topic of their own (value vs reference passing, defaults, named arguments), which is the _Method Parameters_ lesson.
A method can return a value. Use return to send it back:
The return type decimal says "this method gives back a decimal." The return statement specifies which value to give back. Once return runs, the method ends immediately, even if there are more statements after it. Return types get their own lesson (the _Return Types & Tuples_ lesson), including how to return multiple values at once using tuples.
A common pattern combines the two: take inputs, do a calculation, return the result. That's CalculateSubtotal. Another common pattern takes inputs, does something visible (print, save, send), and returns nothing. That's PrintWelcome and GreetCustomer.
void MethodsA method with no return value uses the return type void. The word is a keyword, not a regular type. It means "this method doesn't produce a value; calling it is a statement, not an expression."
You can still use return inside a void method, but with no value after it. It just ends the method early:
The early return lets the method exit when the discount is zero or negative, skipping the rest of the body. This is a common pattern called a guard clause: check the unwanted cases first and bail out, leaving the main flow clean.
What you can't do is assign the result of a void method to a variable:
Since void means "nothing," there's nothing to assign. The compiler stops you at build time. Use void for methods whose purpose is the side effect (printing, updating state, sending a message), not the answer.
Calling a method runs its body and then resumes execution at the line after the call. The flow looks like this:
Two examples make this concrete. The first call returns a value; the second doesn't:
The runtime hits CalculateTotal(...), jumps into the body, runs the three statements, returns 53.98m, and the call site receives that value. Then the program continues with the next line.
Calls can be nested. A method's result can feed directly into another call:
C# evaluates the inner call first (CalculateTotal runs and returns 53.98m), then passes that to PrintTotal. Nesting reads inside-out: the deepest call runs first.
A method can call other methods. Calling another method is just another statement inside the body:
Three small methods, each with one job, composed by a fourth method that coordinates them. This is the everyday shape of C# code: lots of short methods that call each other, instead of one giant method that does everything.
=>)When a method body is a single expression, C# offers a shorter syntax called an expression-bodied member. Instead of { return expr; }, you write => expr;. The two forms are equivalent.
The long form:
The expression-bodied form:
Both compile to the same code. Try them side by side:
Expression-bodied form works for void methods too, as long as the body is a single statement:
The single statement here is the Console.WriteLine call, which is a statement that doesn't produce a value. The => form treats it the same way the block form would.
Where the expression-bodied form doesn't fit:
return or a conditional path.A method like this can't use =>, because the body is two statements:
A reasonable rule of thumb: use => when the method is a true one-liner whose body is just a calculation or a delegation. Use the block form when there are multiple steps, local variables, or branching. The point of => is to reduce noise for the short cases, not to compress logic.
Cost: Expression-bodied methods are pure syntax. The compiler emits the same IL as the block form, so there is no runtime difference. Pick whichever form makes the method easier to read.
static MethodsThe static keyword on a method means "this method is not tied to any specific object." You call it through the type that contains it, not through an instance.
In the examples so far, every method has been static. That's because top-level statement programs (the kind we've been writing) put their methods at the file level, and those methods are implicitly part of a generated class. Without static, the compiler couldn't call them from the top-level entry point, because there's no instance of that generated class to call them on.
The contrast becomes clearer once classes are involved (the _Classes & Objects_ lesson). For now, the rule that matters is: when you write small programs in a single file, with top-level statements as your entry point, declare every method `static`.
Without static, the compiler reports an error like CS0120: An object reference is required for the non-static field, method, or property 'CalculateOrderTotal(decimal, int)'.
Built-in methods you've already used follow the same pattern. Console.WriteLine is a static method on the Console class, which is why you call it as Console.WriteLine(...) rather than creating a Console object first. Math.Max, int.Parse, and string.IsNullOrEmpty are all static for the same reason: they don't need any object state to do their job, just the inputs you hand them.
Each of those calls follows the pattern TypeName.MethodName(arguments). Your own static methods declared in top-level statements work the same way, with the type name being supplied implicitly by the compiler.
A short summary of the rules at this stage:
| Context | Modifier to use |
|---|---|
| Method declared alongside top-level statements | static |
| Method inside a class, callable without an instance | static |
| Method inside a class, operates on a specific instance | no static |
For methods at this stage, treat static as a required keyword in the programs you write.
Every C# method ultimately lives inside a class (or another type like a struct, record, or interface). There is no such thing as a free-floating method that belongs to nothing. What's been happening in the examples is that top-level statements hide this fact: the compiler wraps your file in a generated class behind the scenes, and your static methods become members of that class.
Here is a top-level program:
And here is the equivalent classic form, where the class and entry point are written by hand:
Output (both forms):
Two things differ between the forms. First, the top-level version doesn't write the class Program wrapper or the static void Main() entry point; the compiler generates both. Second, the methods in the top-level version end up as members of that generated class. The behavior at runtime is identical.
For the rest of this section's chapters, you'll see both forms. Short examples use top-level statements because the boilerplate gets in the way. Examples that show multiple methods cooperating, or that mirror how production code is organized, sometimes show the explicit class form. Whichever form you read, remember that methods always live inside a type.
A diagram for the mental model:
One file, one class (in this simple case), many methods, with the entry point calling into the rest.
C# has a strong, widely followed convention for method names. Following it makes your code look like every other C# codebase, which matters more than you might think when working with teammates or reading docs.
The rules:
CalculateSubtotal, not calculateSubtotal or calculate_subtotal.GetCustomer, SaveOrder, ApplyDiscount, IsAvailable. Avoid noun-only names like Customer or Order for methods; those are class names.IsInStock, HasShipped, CanCancel. The name reads like a question, which matches how it'll be used in an if.Id, Url). CalculateSub is worse than CalculateSubtotal.Process and Handle tell you nothing. ProcessOrder is a little better. PlaceOrder is even better.A small gallery of names that follow the convention:
| Method name | What it likely does |
|---|---|
CalculateSubtotal | Returns the sum of price times quantity |
ApplyDiscount | Reduces a total by some discount |
PlaceOrder | Creates and records an order |
IsInStock | Returns a bool for whether stock is available |
HasShipped | Returns a bool for whether shipping has started |
GetCustomerEmail | Looks up an email by customer ID |
TrySaveOrder | Tries to save and returns success without throwing |
FormatPrice | Turns a decimal into a display string |
And a few that don't:
| Bad name | Why |
|---|---|
calc | Lowercase start, abbreviated, vague |
DoStuff | Tells you nothing about what it does |
Order | Looks like a class name, not an action |
process_order | Wrong casing style for C# |
CalcSubAndUpdtCart | Crammed, abbreviated, does two things |
The last one points at another rule that follows from the conventions: one job per method. If you find yourself wanting to put "And" in a name, that's usually a sign the method should be split into two. CalculateSubtotal and UpdateCart, called in sequence, are easier to read, test, and change than a single CalcSubAndUpdtCart.
Parameter names use camelCase: lowercase first letter, capital starts for subsequent words. productName, not ProductName or product_name. The casing makes them visually distinct from method and type names inside the body.
The method name is CalculateOrderTotal (PascalCase, verb-first, specific). The parameters are unitPrice, quantity, and discountRate (camelCase, descriptive). The local variables subtotal and discount also use camelCase, matching the parameter style.
A worked example that uses every idea from this chapter. The program prints a small receipt for an order, breaking the work into named methods.
Look at what the example uses:
=>) and block-bodied ({ ... }) forms, chosen by body length.void and value-returning methods.PrintHeader, CalculateSubtotal, PrintFooter).unitPrice, qty, amount, rate, label, customer).If you wanted to change the formatting of every line, you'd edit PrintLine once and every line in the output would update. If you wanted to add tax, you'd add CalculateTax next to CalculateDiscount and adjust the orchestration at the top. That's the payoff for splitting code into methods: each piece is small, named, and changeable without disturbing the rest.
The pieces this chapter intentionally didn't cover:
ref, out, and in parameter modifiers. The _ref, out & in Parameters_ lesson.params. The _params Keyword_ lesson.Each of those builds on what you've seen here. Methods at their core, declaration, calling, anatomy, naming, static, expression-bodied, and where they live, are everything in this chapter. The _Method Parameters_ lesson zooms in on the part inside the parentheses.
void method returns no value. Use return; with no value to exit early.=> expr;) is shorthand for a body that is a single expression or statement. The compiler emits identical IL for both forms.static. Without static, it becomes a local function instead of a class member.CalculateSubtotal), camelCase for parameter names (unitPrice), and prefer verb-first, specific names. Boolean methods read as questions (IsInStock, HasShipped).The next chapter, Method Parameters, digs into the part inside the parentheses: how values are passed in, the difference between value-type and reference-type parameter behavior, default values, and named arguments.