C# Methods and Parameters Explained — How to Write Reusable Code
Every serious C# application — from a banking app to a video game — is built from hundreds of small, named blocks of logic. Those blocks are called methods. Without them, you'd write the same lines of code over and over, and changing one thing would mean hunting through thousands of lines to fix every copy. Methods are how professional developers stay sane.
The real problem methods solve is repetition and complexity. Imagine calculating a 20% tip on a restaurant bill. If you write that calculation in five different places in your app and the tax rules change, you have to update five places and hope you don't miss one. Wrap that logic in a method called CalculateTip, and you only ever change it in one place. That's the power of methods — write once, use everywhere.
By the end of this article you'll be able to define your own methods, pass data into them using parameters, get data back out using return values, and understand the difference between passing data by value versus by reference. These are skills you'll use in literally every C# program you ever write.
What Is a Method? Anatomy of Your First C# Method
A method is a named block of code that performs one specific job. Think of it as a named recipe card. The card has a title (the method name), a list of ingredients it needs (parameters), a set of steps to follow (the method body), and sometimes a finished dish it hands back to you (the return value).
Every method in C# follows the same structure:
[access modifier] [return type] [MethodName]([parameters]) { ... }
The access modifier (like public or private) controls who can call the method — think of it as 'who is allowed to use this recipe.' The return type tells C# what kind of data the method will hand back when it's done. If the method doesn't hand anything back, you use the keyword void, which literally means 'nothing.'
The method name should be a clear verb phrase — it describes the action being performed. C# convention uses PascalCase for method names, meaning every word starts with a capital letter: CalculateTotal, SendEmail, PrintReport.
Once a method is defined, you execute it by calling it — writing its name followed by parentheses. That's the moment C# jumps into that block of code, runs it, and comes back.
using System; class GreetingApp { // This method has no parameters and returns nothing (void). // It just performs an action — printing a greeting. static void PrintWelcomeMessage() { Console.WriteLine("Welcome to TheCodeForge!"); Console.WriteLine("Let's learn C# methods together."); } static void Main(string[] args) { // Calling the method — C# jumps up, runs the code, then comes back here. PrintWelcomeMessage(); Console.WriteLine("Back in Main after the method finished."); // You can call the same method as many times as you want. // This is the whole point — write once, reuse everywhere. PrintWelcomeMessage(); } }
Let's learn C# methods together.
Back in Main after the method finished.
Welcome to TheCodeForge!
Let's learn C# methods together.
Parameters and Arguments — Feeding Data Into Your Methods
A method with no parameters is like a vending machine with only one button — limited. Parameters let you pass information into a method so it can work with different data each time it's called. This is what makes methods genuinely reusable.
Here's the distinction that trips up beginners: a parameter is the variable declared in the method signature (the placeholder), while an argument is the actual value you pass when you call the method. Parameter is the label on the ingredient slot. Argument is the actual ingredient you drop in.
You can define multiple parameters by separating them with commas. Each parameter needs both a type and a name. The type tells C# what kind of data to expect — string for text, int for whole numbers, double for decimals, bool for true/false.
Methods can also return a value back to the caller using the return keyword. When a method has a return type other than void, it MUST hit a return statement that hands back a value of the correct type. Think of it as the vending machine dispensing your snack — the machine must always give something back when you've paid.
Return values are powerful because the caller can store them in a variable, use them in a calculation, or pass them straight into another method.
using System; class CafeOrderCalculator { // This method takes two parameters: // - itemPrice: the cost of one item (a decimal number) // - quantity: how many the customer wants (a whole number) // It returns a double — the total cost before tax. static double CalculateSubtotal(double itemPrice, int quantity) { double subtotal = itemPrice * quantity; // core calculation return subtotal; // hand the result back to whoever called us } // This method takes a subtotal and applies a tax rate percentage. // taxRatePercent is passed as a number like 8.5 meaning 8.5%. static double ApplyTax(double subtotal, double taxRatePercent) { double taxAmount = subtotal * (taxRatePercent / 100); return subtotal + taxAmount; // total including tax } // Formats the final bill as a readable string for the receipt. static string FormatReceiptLine(string itemName, double totalCost) { // $ before the variable name is NOT a C# thing — this is inside a string. // The colon F2 means: format as a number with 2 decimal places. return $"{itemName}: ${totalCost:F2}"; } static void Main(string[] args) { // "coffeePrice" and "numberOfCoffees" are ARGUMENTS — real values passed in. double coffeePrice = 3.50; int numberOfCoffees = 4; // The return value of CalculateSubtotal is stored in a new variable. double coffeeSubtotal = CalculateSubtotal(coffeePrice, numberOfCoffees); Console.WriteLine($"Subtotal for {numberOfCoffees} coffees: ${coffeeSubtotal:F2}"); // Pass the subtotal into the next method — chaining method calls. double totalWithTax = ApplyTax(coffeeSubtotal, 8.5); Console.WriteLine($"Total with 8.5% tax: ${totalWithTax:F2}"); // Pass the result into FormatReceiptLine for a clean receipt string. string receiptLine = FormatReceiptLine("Flat White x4", totalWithTax); Console.WriteLine("--- Receipt ---"); Console.WriteLine(receiptLine); } }
Total with 8.5% tax: $15.19
--- Receipt ---
Flat White x4: $15.19
Optional Parameters, Named Arguments, and Method Overloading
Real-world methods often need flexibility. C# gives you three tools for this: optional parameters, named arguments, and method overloading.
Optional parameters let you define a default value for a parameter. If the caller doesn't pass a value for it, the default kicks in automatically. You set a default by writing = value in the parameter list. Optional parameters must always come after required parameters — you can't have a required parameter after an optional one.
Named arguments let the caller explicitly state which parameter they're targeting, making code far more readable. Instead of SendEmail('Alice', true, false), you write SendEmail(recipientName: 'Alice', sendCopyToSelf: true, highPriority: false). It's longer but crystal clear.
Method overloading means writing multiple methods with the same name but different parameter lists. C# figures out which version to call based on the arguments you pass. This is how Console.WriteLine works — it accepts a string, or an int, or a double — they're all different overloads of the same method name. The rule is that overloaded methods must differ in the number or types of parameters, not just the return type.
using System; class NotificationService { // Optional parameter: 'isUrgent' defaults to false if not provided. // Required parameter 'recipientName' must always be supplied — it has no default. static void SendNotification(string recipientName, string message, bool isUrgent = false) { string prefix = isUrgent ? "[URGENT] " : ""; // ternary: if urgent add prefix Console.WriteLine($"To: {recipientName} | {prefix}{message}"); } // OVERLOAD 1: Calculate area of a square — only needs one measurement. static double CalculateArea(double sideLength) { Console.WriteLine("Calculating square area..."); return sideLength * sideLength; } // OVERLOAD 2: Calculate area of a rectangle — needs two measurements. // Same method name, different parameters — C# picks the right one automatically. static double CalculateArea(double width, double height) { Console.WriteLine("Calculating rectangle area..."); return width * height; } static void Main(string[] args) { // --- Optional Parameters --- // Calling without the optional parameter — isUrgent defaults to false. SendNotification("Alice", "Your order has shipped."); // Calling with the optional parameter explicitly set to true. SendNotification("Bob", "Server is down!", true); // Named argument — we skip the positional rule and name the parameter. // This is especially helpful when a method has many parameters. SendNotification(recipientName: "Carol", message: "Meeting at 3pm", isUrgent: false); Console.WriteLine(); // --- Method Overloading --- // C# sees one argument — calls the square overload. double squareArea = CalculateArea(5.0); Console.WriteLine($"Square area: {squareArea} sq units"); Console.WriteLine(); // C# sees two arguments — calls the rectangle overload. double rectangleArea = CalculateArea(4.0, 7.5); Console.WriteLine($"Rectangle area: {rectangleArea} sq units"); } }
To: Bob | [URGENT] Server is down!
To: Carol | Meeting at 3pm
Calculating square area...
Square area: 25 sq units
Calculating rectangle area...
Rectangle area: 30 sq units
Value vs Reference Parameters — The Difference That Catches Everyone Out
This is the concept that trips up almost every beginner, so pay close attention. When you pass a variable into a method, C# has two fundamentally different ways of handling it.
By default, C# passes value types (like int, double, bool, char) by value — meaning the method receives a copy of the data. The original variable back in the calling code is completely safe. The method can scribble all over its copy and nothing outside changes. It's like giving someone a photocopy of your document — they can mark it up, but your original is untouched.
Reference types (like string, arrays, and objects you create from classes) behave differently — the method receives a reference pointing to the same data in memory. Changes made inside the method can affect the original. It's like giving someone your only copy of the document — they change it, you see the changes.
But C# also gives you explicit keywords to control this: ref and out. The ref keyword forces a value type to be passed by reference — the method can both read AND modify the original. The out keyword is similar, but it's designed for when the method needs to return multiple values — the variable doesn't need a value before being passed in, but the method is required to assign it one before returning.
using System; class ParameterPassingDemo { // VALUE type parameter — receives a COPY of the original. // Doubling the copy does NOT affect the original variable. static void TryToDoubleScore(int playerScore) { playerScore = playerScore * 2; // only the local copy changes Console.WriteLine($" Inside method, playerScore = {playerScore}"); } // REF parameter — receives a REFERENCE to the original. // Doubling here WILL affect the original variable. static void ActuallyDoubleScore(ref int playerScore) { playerScore = playerScore * 2; // the original changes! Console.WriteLine($" Inside method (ref), playerScore = {playerScore}"); } // OUT parameter — used to return multiple results from one method. // The caller doesn't need to initialise 'quotient' or 'remainder' beforehand. // But this method MUST assign them before it returns. static void DivideWithRemainder(int dividend, int divisor, out int quotient, out int remainder) { quotient = dividend / divisor; // integer division remainder = dividend % divisor; // modulo gives the leftover } static void Main(string[] args) { // --- Value Parameter Demo --- int currentScore = 100; Console.WriteLine("-- Value Parameter (copy) --"); Console.WriteLine($"Before call: currentScore = {currentScore}"); TryToDoubleScore(currentScore); // passes a copy Console.WriteLine($"After call: currentScore = {currentScore}"); // unchanged! Console.WriteLine(); // --- Ref Parameter Demo --- Console.WriteLine("-- Ref Parameter (original) --"); Console.WriteLine($"Before call: currentScore = {currentScore}"); ActuallyDoubleScore(ref currentScore); // 'ref' required at both definition AND call site Console.WriteLine($"After call: currentScore = {currentScore}"); // changed! Console.WriteLine(); // --- Out Parameter Demo --- Console.WriteLine("-- Out Parameter (multiple returns) --"); int howManyTimes; int leftOver; // 'out' keyword required at the call site too — clear signal that values will be set. DivideWithRemainder(17, 5, out howManyTimes, out leftOver); Console.WriteLine($"17 divided by 5 = {howManyTimes} remainder {leftOver}"); } }
Before call: currentScore = 100
Inside method, playerScore = 200
After call: currentScore = 100
-- Ref Parameter (original) --
Before call: currentScore = 100
Inside method (ref), playerScore = 200
After call: currentScore = 200
-- Out Parameter (multiple returns) --
17 divided by 5 = 3 remainder 2
| Feature | ref parameter | out parameter |
|---|---|---|
| Must be initialised before the call? | Yes — must have a value first | No — can be unassigned |
| Method can READ the incoming value? | Yes | No (value is undefined on entry) |
| Method MUST assign a value before returning? | No — optional | Yes — compiler enforces this |
| Primary use case | Modify an existing value in place | Return multiple values from one method |
| Keyword required at call site? | Yes: ActuallyDouble(ref score) | Yes: Divide(17, 5, out q, out r) |
| Works with value types (int, double)? | Yes | Yes |
🎯 Key Takeaways
- A method is a named, reusable block of code — define it once with a clear verb name, call it as many times as needed. Repetition in code is always a signal to extract a method.
- Parameters are placeholders in the method definition; arguments are the real values you pass at the call site. Getting this distinction right will make documentation and error messages instantly clearer.
- Value types (int, double, bool) are passed as copies by default — the original is safe. To let a method modify the original, use ref. To let a method output multiple results, use out.
- Method overloading lets you reuse the same meaningful name for related behaviours with different inputs — just like Console.WriteLine handles strings, ints, and doubles all under one name.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Forgetting 'ref' or 'out' at the call site — Error: 'Argument must be passed with the ref keyword' — Fix: You must write the ref or out keyword both in the method signature AND at the call site. C# requires it at both places deliberately, so the caller is never surprised that their variable might change.
- ✕Mistake 2: Putting optional parameters before required parameters — Error: 'Optional parameters must appear after all required parameters' — Fix: Always list your required parameters first, then your optional ones. For example: void Send(string recipient, string message, bool urgent = false) is valid. void Send(bool urgent = false, string recipient, string message) will not compile.
- ✕Mistake 3: Expecting a void method to return a value — Error: 'Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration' or simply 'Since the method returns void, a return keyword must not be followed by an expression' — Fix: If your method needs to send data back to the caller, change void to the appropriate return type (int, string, double, etc.). If you just want to exit the method early without returning a value, use a bare return; statement with no value after it.
Interview Questions on This Topic
- QWhat is the difference between a parameter and an argument in C#? Can you give a concrete example?
- QExplain the difference between passing a parameter by value and passing it by reference. When would you choose ref over a regular parameter?
- QWhat is the difference between the ref and out keywords in C#? Can a method with an out parameter skip assigning a value to it before returning — and what happens if it does?
Frequently Asked Questions
What is the difference between void and a return type in a C# method?
void means the method performs an action but hands nothing back to the caller — like a printer that just prints. A specific return type like int or string means the method computes something and gives a result back. If you declare a return type, every code path in the method must end with a return statement that provides a value of that type.
How many parameters can a C# method have?
Technically there's no hard language limit, but in practice you should start to worry once a method has more than three or four parameters — it's usually a sign the method is trying to do too much. When you need many values, consider grouping them into a class or struct and passing that as a single parameter instead.
Can a C# method call itself?
Yes — this is called recursion. A method that calls itself will keep going until it hits a base case that returns without making another call. Recursion is powerful for problems like tree traversal or calculating factorials, but beginners should be careful: forget the base case and you'll get a StackOverflowException as the method calls itself forever.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.