37. Ref, Out, and In Keywords in C#
Level: Intermediate to Advanced
Goal: Pass variables by reference for efficiency and returning multiple values from methods.
Normally, when you pass a variable to a method, C# makes a copy:
int age = 20;
IncrementAge(age); // Method gets a copy
// age is still 20
With ref, the method can modify the original:
int age = 20;
IncrementAge(ref age); // Method modifies original
// age is now 21
- Pass by value vs pass by reference
refkeyword - modify original variableoutkeyword - return multiple valuesinkeyword - read-only reference (performance)- When to use each keyword
- Common patterns (TryParse, Swap, Multiple returns)
- Performance implications with large structs
- Interview questions on parameter modifiers
Parameter Modifiers Reference
| Keyword | Passes | Must Initialize | Method Must Assign | Can Read | Can Write | Use Case | Example |
|---|---|---|---|---|---|---|---|
| (none) | Value (copy) | N/A | N/A | ? Yes | ? No | Simple parameter | void Add(int x) |
ref | Reference | ? Yes | ? No | ? Yes | ? Yes | Modify original value | void Swap(ref int a, ref int b) |
out | Reference | ? No | ? Yes | ? Only after assign | ? Yes | Return multiple values | bool TryParse(string s, out int result) |
in | Reference (read-only) | ? Yes | ? No | ? Yes | ? No | Performance, large structs | double Calculate(in LargeStruct s) |
Quick Definitions
- Pass by value - Copy value passed to method (original unchanged)
- Pass by reference - Pass address, method can modify original
refkeyword - Pass by reference, read AND writeoutkeyword - Pass by reference, method MUST assign valueinkeyword - Pass by reference, read-only (performance)- Parameter modifier -
ref,out,inkeywords - Return value - What method returns with
returnstatement - Multiple returns - Use
outto return multiple values - Struct - Value type (unlike class, which is reference type)
- Performance - Avoid copying large objects with
inmodifier
Default: Pass by Value
// Without any modifier - value is COPIED
static void AddBonus(double salary)
{
salary += 5000; // changes COPY only
}
double teacherSalary = 50000;
AddBonus(teacherSalary);
Console.WriteLine(teacherSalary); // still 50000 - unchanged
ref - Pass by Reference (Read + Write)
ref passes the original variable - changes inside method affect the caller.
static void AddBonus(ref double salary)
{
salary += 5000; // changes ORIGINAL
}
double teacherSalary = 50000;
AddBonus(ref teacherSalary);
Console.WriteLine(teacherSalary); // 55000 ?
// Rules:
// ? Variable MUST be initialized before passing
// ? Caller must use ref keyword too
// ? Method can READ and WRITE the value
School Management - Swap Marks
static void SwapMarks(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int english = 85, maths = 91;
Console.WriteLine($"Before: English={english}, Maths={maths}");
SwapMarks(ref english, ref maths);
Console.WriteLine($"After : English={english}, Maths={maths}");
// After: English=91, Maths=85
// Real use - update student attendance count
static void MarkPresent(ref int presentCount, ref int totalDays)
{
presentCount++;
totalDays++;
}
int present = 20, total = 25;
MarkPresent(ref present, ref total);
Console.WriteLine($"Attendance: {present}/{total}");
out - Output Parameter (Write Only)
out passes a variable that the method MUST assign. Used to return multiple values.
static void CalculateResult(
int[] marks,
out double percentage,
out string grade,
out bool passed)
{
percentage = marks.Average();
if (percentage >= 90)
{
grade = "A+";
}
else if (percentage >= 80)
{
grade = "A";
}
else if (percentage >= 70)
{
grade = "B";
}
else if (percentage >= 60)
{
grade = "C";
}
else if (percentage >= 35)
{
grade = "D";
}
else
{
grade = "F";
}
passed = percentage >= 35;
// MUST assign all out params - compiler error otherwise
}
// Usage
int[] studentMarks = { 82, 91, 78, 85, 88 };
CalculateResult(studentMarks, out double pct, out string grade, out bool passed);
Console.WriteLine($"Percentage: {pct:F1}% | Grade: {grade} | Passed: {passed}");
// Inline out variable (C# 7+) - no pre-declaration needed
if (int.TryParse("85", out int marks))
Console.WriteLine($"Valid marks: {marks}");
// Discard out param you don't need with _
CalculateResult(studentMarks, out double percentage, out _, out bool isPass);
Rules: ref vs out
ref | out | |
|---|---|---|
| Must initialize before call | Yes | No |
| Method must assign | Not required | Yes, must |
| Can read inside method | Yes | Only after assign |
| Use case | Modify existing value | Return multiple values |
in - Read-Only Reference (C# 7.2+)
in passes by reference but is read-only inside the method. Used for performance with large structs.
// Large struct - copying is expensive
public struct ExamResult
{
public string StudentName;
public int EnglishMarks;
public int MathsMarks;
public int ScienceMarks;
public int SocialMarks;
public int HindiMarks;
// ... more fields
}
// Without in - struct COPIED on every call (expensive)
static double GetPercentage(ExamResult result)
{
return (result.EnglishMarks + result.MathsMarks +
result.ScienceMarks + result.SocialMarks +
result.HindiMarks) / 500.0 * 100;
}
// With in - passed by reference, no copy, read-only
static double GetPercentageFast(in ExamResult result)
{
// result.EnglishMarks = 100; <- compile error - read only
return (result.EnglishMarks + result.MathsMarks +
result.ScienceMarks + result.SocialMarks +
result.HindiMarks) / 500.0 * 100;
}
var exam = new ExamResult { StudentName="Sahasra", EnglishMarks=82,
MathsMarks=91, ScienceMarks=78, SocialMarks=85, HindiMarks=88 };
double pct = GetPercentageFast(in exam);
Console.WriteLine($"{exam.StudentName}: {pct:F1}%");
Summary Comparison
Common Mistakes
? Forgetting ref in caller when method expects it:
static void AddBonus(ref double salary) => salary += 5000;
double pay = 50000;
AddBonus(pay); // Error - forgot ref keyword
? Use ref both in method and caller:
AddBonus(ref pay); // ? Correct
? Using out for parameter that method doesn't initialize:
static void Process(out string message)
{
// Forgot to assign message
Console.WriteLine("Processing");
// Error - must assign all out params
}
? Always assign out parameters before return:
static void Process(out string message)
{
message = "Processing complete"; // ? Assigned
}
? Using ref when out more appropriate:
// Method returns two values - use out, not ref
static void Calculate(int a, int b, ref int sum, ref int product)
{
sum = a + b;
product = a * b;
}
? Use out for computed return values:
static void Calculate(int a, int b, out int sum, out int product)
{
sum = a + b;
product = a * b;
}
? Modifying in parameter (compiler catches this):
static void Process(in ExamResult result)
{
result.Marks = 100; // Error - in is read-only
}
? Treat in as read-only:
static void Process(in ExamResult result)
{
Console.WriteLine(result.Marks); // ? Reading OK
}
? Not using in for large structs (copies on each call):
// Large struct, called frequently - should use in
static bool Validate(LargeConfiguration config) // Copied each call!
{
return config.IsValid;
}
? Use in for large readonly structs:
static bool Validate(in LargeConfiguration config) // No copy, passed by ref
{
return config.IsValid;
}
When You'll Use This in SMS
SMS uses ref and out for specific scenarios:
// out - TryParse pattern (return + output)
public bool TryEnrollStudent(string rollNumber, out Student student)
{
student = null;
var found = _studentRepo.FindByRoll(rollNumber);
if (found == null)
return false; // student still null here
student = found; // Must assign before success
return true;
}
// Usage
if (TryEnrollStudent("NCA-2024-0001", out var student))
Console.WriteLine($"Enrolled: {student.Name}");
// ref - swap values efficiently
public static void SwapStudentMarks(ref double marks1, ref double marks2)
{
double temp = marks1;
marks1 = marks2;
marks2 = temp;
}
// in - pass large result struct without copying
public decimal CalculateFinalMarks(in StudentResult result)
{
return result.InternalMarks + result.ExternalMarks;
// result not copied (performance win on large struct)
}
Real impact: out reduces boilerplate, ref enables swaps/modifications, in avoids struct copying overhead.
Try This Now
- Create
TryGetStudentByRollusingoutpattern - Implement
Swapmethod usingrefon two variables - Use
inmodifier on large struct parameter - Call each method and observe behavior
- Measure performance difference with/without
in
Parameter modifiers explained: ref vs out vs in, pass by value vs reference, TryParse pattern, when to use each. Video coming soon. Subscribe to NexCoding YouTube for updates.
Best Practices
- Use value parameters by default - Simpler, safer, less confusion
- Use
refwhen method needs to modify original - Swap, increment counters - Use
outfor returning multiple values - Alternative to returning tuple - Use
infor large readonly structs - Avoid copying overhead - Always check for
nullwithref/out- Null reference still possible (for classes) - Document what
ref/outdo - Caller needs to know value changes - Prefer return value over
out- Clearer intent, works with LINQ - Use
outwithTryPattern-TryParse,TryGetValuestandard approach - Don't use
reffor performance - Only useinfor large structs - Test parameter modifications carefully - Easy to forget
refin tests - Avoid
refchains - Don't passrefparameter to anotherrefparameter - Consider tuples as alternative to multiple
out-(bool, int, string)vs threeoutparams
ref: Original variable must be initialized before call. Method can READ and WRITE. Used to modify existing value.
out: Variable doesn't need to be initialized. Method MUST assign value. Used to return multiple values.
static void ModifyValue(ref int x) => x *= 2; // ref - modify existing
static bool TryGetValue(out int x)
{
x = 100; // out - assign return value
return true;
}
int num = 50;
ModifyValue(ref num); // num initialized first
int result;
TryGetValue(out result); // result doesn't need to be initialized
Rule: ref for modification, out for returning.
Use ref when: modifying mutable object, swapping values, updating collection element in-place.
Prefer return when: method computes new value, more readable, works with LINQ.
// ? Appropriate for ref - modifying in-place
static void SwapValues(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
// ? Don't use ref - return is clearer
static int AddBonus(int salary)
{
return salary + 5000; // Better than ref for computation
}
// ? Appropriate for ref - updating counter
static void IncrementCount(ref int count) => count++;
Use ref sparingly - return is clearer for most cases.
Value types (int, double) can't be null in C# < 8. TryParse needs way to return:
- Success/failure (bool)
- Parsed value (int)
Can't return null for int. So use out:
int.TryParse("123", out int result); // out for parsed value, return for success
If it returned int? nullable:
int? result = int.TryParse("123"); // Can't call - what about failure case?
Better: Return bool (success), use out for value.
in = pass by reference, read-only. Used for performance with large structs.
Without in - struct COPIED each call (expensive). With in - passed by reference, no copy.
public struct LargeData { } // Lots of fields
// Without in - copies struct on every call
static void Process(LargeData data) { } // Expensive!
// With in - passes reference, no copy, read-only
static void Process(in LargeData data) { } // Fast!
Only use in when: struct is large (100+ bytes), method doesn't modify it, called frequently.
Yes, if parameter type allows null (reference type or nullable value type).
static void Process(ref int? x) => x = 100; // int? allows null
int? value = null;
Process(ref value); // Passing null - OK
But with non-nullable reference types warning:
static void Process(ref string name) => name = "New";
string? s = null;
Process(ref s); // WARNING - passing nullable to non-nullable ref
Be careful: ref doesn't prevent null, only adds reference passing.
- ref/out: No performance difference from regular parameters (both pass reference). Used for semantics (modify existing vs return new).
- in: Performance benefit with large structs - avoids copy overhead.
// Large struct - 1000+ bytes
public struct Config { /* many fields */ }
// Without in - copies 1000+ bytes on each call
static void Setup(Config cfg) { } // Copies Config
// With in - passes reference, no copy (8-16 bytes pointer)
static void Setup(in Config cfg) { } // Just passes pointer
Benchmark: in can be 10-100x faster for large structs. Don't use ref/out for performance - only in.
Use ChatGPT, Claude, or Copilot to go deeper on C# ref, out, in parameter modifiers. Try these prompts:
"What is the difference between ref and out in C# with a real example?""When should I use ref vs returning a value from a method?""Why does int.TryParse use out instead of returning null?""Give me 3 practical scenarios where out parameters are better than returning a tuple"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.