Back to Blog
C#

C# Method Parameters: ref, out, in, params

c# method parameters: Learn how C# method parameters work: value, ref, out, in, and params. Understand semantics, use cases, and performance tradeoffs.

C#ref keywordout keywordparams keywordin parameter
Diagram illustrating C# method parameter passing semantics for ref, out, in, and params.

c# method parameters requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Every C# method has a signature that defines how data flows in and out. The default behavior is pass-by-value: a copy of the argument is made. But C# also provides ref, out, in, and params modifiers to change that behavior. Choosing the right modifier affects correctness, performance, and code clarity. This article explains each parameter type, when to use it, and what can go wrong.

Value Parameters: The Default

When you declare a parameter without a modifier, the method receives a copy of the argument. For value types like int, double, or struct, that means the method works on a separate copy. Modifying the parameter inside the method does not affect the caller's variable.

public static void Increment(int number) { number++; } int x = 5; Increment(x); Console.WriteLine(x); // still 5

For reference types like classes, the reference itself is passed by value. The method can modify the object's state, but reassigning the parameter does not change the caller's reference.

public static void ChangeName(Person person) { person.Name = "Alice"; // affects the original object person = new Person(); // no effect on the caller's variable }

This default behavior is safe and predictable. Use value parameters unless you have a specific reason to share storage.

Using ref to Modify the Caller's Variable

The ref keyword passes a reference to the caller's variable, not a copy. The method can read and modify the variable directly. The argument must be initialized before the call.

public static void Swap(ref int a, ref int b) { int temp = a; a = b; b = temp; } int first = 1; int second = 2; Swap(ref first, ref second); Console.WriteLine($"{first} {second}"); // 2 1

ref is useful when you need to update multiple values or when you want to avoid copying a large struct. However, it makes the method dependent on the caller's storage, which can reduce encapsulation. Use it deliberately.

Using out for Definite Assignment

The out keyword is similar to ref but with a key difference: the method must assign a value to the parameter before returning. The caller does not need to initialize the variable beforehand.

public static bool TryParseInt(string input, out int result) { if (int.TryParse(input, out result)) { return true; } result = 0; // must assign even on failure return false; } if (TryParseInt("42", out int value)) { Console.WriteLine(value); // 42 }

The compiler enforces definite assignment. This pattern is common for methods that return a success flag and an output value, avoiding the need for a tuple or a custom result type.

Using in for Read-Only References

The in modifier passes a reference to the argument but prevents the method from modifying it. This is useful for large structs where copying is expensive but you want read-only access.

public static double Distance(in Point p1, in Point p2) { double dx = p1.X - p2.X; double dy = p1.Y - p2.Y; return Math.Sqrt(dx * dx + dy * dy); }

The in parameter is passed by reference, but the compiler enforces that the method does not assign to it. This can reduce memory traffic for large structs, but for small types like int or double, the overhead of indirection may outweigh the benefit. Use in only when profiling shows a need.

Using params for Variable-Length Arguments

The params keyword allows a method to accept a variable number of arguments. The parameter must be a single-dimensional array and must be the last parameter in the signature.

public static int Sum(params int[] numbers) { int total = 0; foreach (int n in numbers) { total += n; } return total; } int sum = Sum(1, 2, 3, 4); // 10

When you call the method, the compiler creates an array from the arguments. You can also pass an array directly. This is convenient for methods like string.Format or Console.WriteLine that accept a variable number of arguments.

Optional Parameters and Named Arguments

Optional parameters have default values, so the caller can omit them. Named arguments let you specify arguments by name, which is useful when you want to skip earlier optional parameters.

public static void Configure(string host, int port = 80, bool useTls = true) { // implementation } Configure("example.com"); Configure("example.com", 443); Configure("example.com", useTls: false);

Default values must be compile-time constants. Named arguments can appear in any order after positional arguments. This feature improves readability when a method has many parameters, but it can also hide breaking changes if you reorder parameters later.

Choosing the Right Parameter Type

Parameter TypeSemanticsTypical Use Case
Value (default)Copy of argumentSimple data input, no side effects
refReference to caller's variableModify multiple variables, avoid copying large structs
outReference, must assignReturn additional values, try-parse patterns
inRead-only referenceLarge structs where copying is costly
paramsVariable-length arrayMethods that accept a flexible number of arguments

Use ref and out sparingly. They make the method more tightly coupled to the caller and can make the code harder to follow. Prefer returning a tuple or a custom result object when you need multiple outputs. Use in only when you have measured a performance problem with large structs.

Common Mistakes and Pitfalls

One frequent mistake is forgetting to initialize an out variable before the method returns. The compiler catches this, but it can still confuse developers who expect the method to leave the variable untouched on failure.

Another issue is using ref on value types that are small, like int. The overhead of indirection and the risk of unintended side effects usually outweigh any benefit. Similarly, using in on a simple int or double can make the code slower because the JIT may not optimize the indirection as well as a direct copy.

With params, be aware that the compiler allocates a new array on every call unless you pass an array explicitly. In a hot loop, this can create garbage. If the number of arguments is fixed, use a regular parameter instead.

Performance and Maintainability Considerations

The primary performance concern with method parameters is copying large structs. Passing a large struct by value copies all its fields, which can be expensive. ref, out, and in avoid that copy. However, ref and out also allow mutation, which can introduce bugs if the method changes the caller's variable unintentionally. in provides read-only access, so it is safer for large structs that should not change.

Maintainability suffers when a method has many parameters, especially if they are the same type. Named arguments help at the call site, but consider introducing a parameter object or a configuration class when the parameter list grows beyond four or five items. This reduces cognitive load and makes the method easier to test.

Another maintainability concern is the interaction between optional parameters and named arguments. Changing a default value is a binary breaking change for callers that rely on it. Adding a new optional parameter in the middle of the list can break existing named arguments. Prefer adding new overloads or a separate method when the signature is likely to evolve.

Finally, remember that params arrays are mutable inside the method. If you store the array or modify its elements, the caller may see unexpected changes if they passed an array directly. Copy the array if you need to protect the caller's data.

In summary, C# method parameters give you fine-grained control over data flow. Start with value parameters for simplicity, use out for multiple return values, ref only when you must modify the caller's variable, in for large read-only structs, and params for variable-length argument lists. The right choice depends on the specific tradeoff between clarity, safety, and performance.

c# method parameters: Practical Usage and Code Examples | RYUSLOG DEV