C# Method Parameters
Method parameters are the named inputs a C# method accepts from its caller. They matter because parameters are how you send data into reusable code while keeping the method independent from the exact variables used by the caller.
Understanding parameters also prevents subtle bugs. In C#, arguments are passed by value by default, but that rule behaves differently for value types and reference types, and C# also provides special parameter modifiers such as out, ref, and params.
Overview: How Method Parameters Work
A parameter is declared inside a method’s parentheses. An argument is the actual value supplied when the method is called. In PrintTotal(25.50m), 25.50m is the argument; in static void PrintTotal(decimal total), total is the parameter.
By default, C# passes arguments by value. That means the method receives its own parameter variable initialized from the argument. If the parameter type is a value type such as int, bool, decimal, DateTime, or a custom struct, the value itself is copied. Assigning a new value to the parameter affects only the method’s local copy.
For reference types such as arrays, strings, lists, dictionaries, and most classes, the reference is copied by value. The method receives a copy of the address-like reference to the same object. Because both references point at the same object, mutating that object can be visible to the caller. However, assigning the parameter to a different object still changes only the local parameter variable.
C# adds several parameter features for common calling patterns. Optional parameters provide default values when the caller omits an argument. Named arguments let the caller specify arguments by parameter name, which can improve readability when several values have the same type. The out modifier lets a method assign a value for the caller, usually while returning a success flag. The ref modifier lets a method read and write the caller’s variable directly. The params modifier lets callers pass a variable number of arguments as though they were an array.
Parameter types are part of overload resolution. Two methods can share a name when their parameter lists differ, and the compiler chooses the best match based on the arguments at the call site. Parameter names are not part of the method signature for overloading, but they do matter for named arguments and readability.
Syntax
static return_type MethodName(type parameterName, type optionalName = defaultValue)
{
statements;
}
static bool TryMethod(inputType input, out resultType result)
{
result = value;
return trueOrFalse;
}
static void ChangeValue(ref type variable)
{
variable = newValue;
}
static void ManyValues(params type[] values)
{
statements;
}
| Form | Meaning |
|---|---|
type parameterName |
A normal parameter. The caller must provide an argument unless a default value is supplied. |
parameter = defaultValue |
An optional parameter. It must come after all required parameters in the same parameter list. |
out |
The method must assign the parameter before returning. The caller does not need to initialize the variable first. |
ref |
The caller must pass an initialized variable with ref. The method can read and replace that variable’s value. |
params |
Collects zero or more trailing arguments into an array. Only one params parameter is allowed, and it must be last. |
Examples
Normal Parameters and Reference-Type Effects
using System;
class Program
{
static void Main()
{
int score = 10;
int[] scores = { 10, 20, 30 };
AddFive(score);
AddFiveToFirst(scores);
ReplaceArray(scores);
Console.WriteLine($"score: {score}");
Console.WriteLine($"first array item: {scores[0]}");
}
static void AddFive(int value)
{
value += 5;
}
static void AddFiveToFirst(int[] values)
{
values[0] += 5;
}
static void ReplaceArray(int[] values)
{
values = new int[] { 0, 0, 0 };
}
}
Output:
score: 10
first array item: 15
AddFive changes only its local copy of the int. AddFiveToFirst changes the array object that both caller and method can reach. ReplaceArray assigns the local parameter to a new array, but the caller’s scores variable still points to the original array.
Optional and Named Arguments
using System;
using System.Globalization;
class Program
{
static void Main()
{
PrintReceipt("Notebook", 5.00m);
PrintReceipt("Backpack", 40.00m, quantity: 2, discountRate: 0.10m);
}
static void PrintReceipt(string item, decimal unitPrice, int quantity = 1, decimal discountRate = 0.00m)
{
decimal subtotal = unitPrice * quantity;
decimal discount = subtotal * discountRate;
decimal total = subtotal - discount;
Console.WriteLine($"{item}: {quantity} x {unitPrice.ToString("0.00", CultureInfo.InvariantCulture)}");
Console.WriteLine($"Total: {total.ToString("0.00", CultureInfo.InvariantCulture)}");
}
}
Output:
Notebook: 1 x 5.00
Total: 5.00
Backpack: 2 x 40.00
Total: 72.00
quantity and discountRate have default values, so the first call omits them. The second call uses named arguments, which makes it clear that 2 is the quantity and 0.10m is the discount rate.
Using out to Return Extra Information
using System;
class Program
{
static void Main()
{
ShowOrder("48");
ShowOrder("abc");
}
static void ShowOrder(string text)
{
if (TryReadOrderId(text, out int orderId))
{
Console.WriteLine($"Order #{orderId} accepted");
}
else
{
Console.WriteLine($"'{text}' is not a valid order id");
}
}
static bool TryReadOrderId(string text, out int orderId)
{
bool parsed = int.TryParse(text, out orderId);
return parsed && orderId > 0;
}
}
Output:
Order #48 accepted
'abc' is not a valid order id
The method returns bool to report success or failure, while the out parameter carries the parsed value when parsing succeeds. This is the same idea used by int.TryParse and Dictionary.TryGetValue.
Using ref for an Intentional In-Place Change
using System;
class Program
{
static void Main()
{
int stock = -3;
ClampToRange(ref stock, 0, 100);
Console.WriteLine($"Stock after clamp: {stock}");
stock = 125;
ClampToRange(ref stock, 0, 100);
Console.WriteLine($"Stock after clamp: {stock}");
}
static void ClampToRange(ref int value, int minimum, int maximum)
{
if (value < minimum)
{
value = minimum;
}
else if (value > maximum)
{
value = maximum;
}
}
}
Output:
Stock after clamp: 0
Stock after clamp: 100
ref means the parameter is an alias for the caller’s variable. Both the method declaration and the call site must say ref, which makes the in-place change visible in the code.
Using params for a Flexible Number of Arguments
using System;
using System.Globalization;
class Program
{
static void Main()
{
PrintAverage("Quiz scores", 8, 9, 10);
PrintAverage("No scores yet");
}
static void PrintAverage(string label, params int[] values)
{
if (values.Length == 0)
{
Console.WriteLine($"{label}: no values");
return;
}
int total = 0;
foreach (int value in values)
{
total += value;
}
double average = (double)total / values.Length;
Console.WriteLine($"{label}: {average.ToString("0.0", CultureInfo.InvariantCulture)}");
}
}
Output:
Quiz scores: 9.0
No scores yet: no values
The params parameter receives an array. The caller can pass individual values, no values, or an existing int[]. Because it collects the remaining arguments, it must be the final parameter.
How Parameters Work Step by Step
- The compiler checks the method call and builds a list of candidate methods with the requested name.
- It compares the supplied arguments with each candidate’s parameter list, including normal conversions, optional parameters, named arguments, and overload rules.
- For each normal parameter, the runtime initializes a parameter variable inside the called method’s stack frame.
- For value types, the contained value is copied. For reference types, the object reference is copied, so object mutation may be shared.
- For
outandref, the method receives access to the caller’s variable storage rather than only an independent local parameter variable. - When the method returns, normal parameters disappear with the stack frame. Changes made through
out,ref, or shared objects can remain visible to the caller.
Common Mistakes
Putting an Optional Parameter Before a Required One
static void CreateUser(string role = "Member", string name)
{
Console.WriteLine(name + ": " + role);
}
This does not compile because optional parameters must come after required parameters. Otherwise, a positional call such as CreateUser("Ada") would be ambiguous to readers and difficult for the compiler to bind cleanly. Put required inputs first:
using System;
class Program
{
static void Main()
{
CreateUser("Ada");
CreateUser("Grace", role: "Admin");
}
static void CreateUser(string name, string role = "Member")
{
Console.WriteLine(name + ": " + role);
}
}
Output:
Ada: Member
Grace: Admin
Forgetting ref at the Call Site
using System;
class Program
{
static void Main()
{
int count = 5;
Reset(ref count);
Console.WriteLine(count);
}
static void Reset(ref int value)
{
value = 0;
}
}
Output:
0
The code above is correct because ref appears both in the method declaration and the call. If the call were written as Reset(count), it would not compile. C# requires the keyword on both sides so readers can see that the method may replace the caller’s variable.
Using ref When a Return Value Is Clearer
using System;
class Program
{
static void Main()
{
int price = 50;
AddTax(ref price);
Console.WriteLine(price);
}
static void AddTax(ref int amount)
{
amount += 5;
}
}
Output:
55
This compiles, but a return value is usually easier to reason about for a calculation. Prefer int total = AddTax(price); unless the operation truly needs to update an existing variable in place.
Best Practices
- Name parameters after their role, such as
customerId,minimum, orincludeInactive, not vague names likexordata. - Keep parameter lists short. When several parameters always travel together, consider a class, record, struct, or options object.
- Use named arguments when a call contains several values of the same type, especially multiple
bool,int, ordecimalarguments. - Prefer return values for ordinary calculations. Use
refonly when mutation of the caller’s variable is the point. - Use
outfor Try-style methods that need to return both success/failure and a produced value. - Put required parameters first, optional parameters after them, and a
paramsparameter last. - Avoid changing mutable objects through parameters unless the method name clearly communicates that side effect.
- Be careful changing default values in public APIs. Optional parameter defaults are often baked into already compiled callers.
Practice Exercises
- Write a method named
FormatAddresswith requiredstreetandcityparameters, plus an optionalcountryparameter with the default"USA". - Write a
TryDividemethod that accepts twodoublevalues and returnsfalsewhen the divisor is zero. Use anout double resultparameter for the answer. - Write a method named
Largestthat usesparams int[] numbersand returns the largest value. Decide what your method should do when no numbers are supplied.
Summary
- Parameters are named inputs declared by a method; arguments are the values supplied by the caller.
- C# passes arguments by value by default, copying either the value itself or a reference to an object.
- Optional and named arguments make calls more flexible and readable when used carefully.
outlets a method assign a caller-visible result, commonly in Try-style APIs.reflets a method read and replace the caller’s variable directly, so it should be used deliberately.paramscollects a variable number of trailing arguments into an array.
