return Statement (C#)
The basic syntax of the return statement for returning a value from a method. In C#, return is an instruction that "terminates method execution and returns control to the caller." This page covers how to use it with different return types, including early returns for void methods, guard clauses, and returning multiple values with tuples.
Syntax
ReturnType MethodName(params) {
// processing
return value; // Returns a value to the caller and exits the method.
}
// return in a void method (no value).
void MethodName(params) {
if (condition) {
return; // Exits the method early without returning a value (early return).
}
// This code runs only when the condition is false.
}
// return with a tuple to return multiple values.
(Type1, Type2) MethodName(params) {
return (value1, value2); // Wraps multiple values in parentheses and returns them.
}
// return with a named tuple.
(Type1 Name1, Type2 Name2) MethodName(params) {
return (value1, value2); // The caller accesses values via .Name1 / .Name2.
}
Return Types and return Syntax
| Return type | return syntax | Notes |
|---|---|---|
void | return; or omitted | The return can be omitted at the end of the method. Use it explicitly when you need to exit early. |
int / double and other value types | return value; | A type mismatch causes a compile error. |
string / class types | return reference; | You can also return null (for nullable types). |
bool | return true; / return false; | Commonly used in condition-checking methods. |
(Type1, Type2) tuple | return (value1, value2); | Returns multiple values at once. Available in C# 7 and later. |
Task<T> | return value; (inside an async method) | In async methods, the returned value is automatically wrapped in a Task. |
Early Return and Guard Clauses
Checking for invalid arguments or error conditions at the top of a method and returning immediately is called an "early return" or "guard clause." This keeps nesting shallow and makes the main logic of the method easier to read.
| Style | Characteristics |
|---|---|
| With early return (guard clause) | Error cases are handled first, and the main logic is placed at the end. Nesting stays shallow. |
| Without early return (if / else nesting) | Both happy and error paths are wrapped in if/else, which tends to increase nesting depth. |
Sample Code
ReturnBasic.cs
using System;
class ReturnBasic {
static string GetLevelName(int level) {
switch (level) {
case 0: return "S";
case 1: return "A";
case 2: return "B";
case 3: return "C";
case 4: return "D";
default: return "Unknown";
}
// This line is never reached because each case has a return statement.
}
// Aborts processing if the score is 0 or less.
static void ShowScore(string name, int score) {
if (score <= 0) {
Console.WriteLine(name + " has no score.");
return; // Exits here. The code below is not executed.
}
// This code runs only when score is 1 or greater.
Console.WriteLine(name + "'s score: " + score);
}
// Returns true if the level is S.
static bool IsLevelS(int level) {
return level == 0; // You can return a conditional expression directly.
}
static void Main() {
// Call GetLevelName.
Console.WriteLine("=== Level Name Lookup ===");
for (int i = 0; i <= 4; i++) {
Console.WriteLine("Level " + i + " -> " + GetLevelName(i));
}
// Verify early return in a void method.
Console.WriteLine("\n=== Score Display ===");
ShowScore("member_2", 1200);
ShowScore("member_6", 0); // Returns early because score is 0.
ShowScore("member_1", 8500);
// Verify the boolean-returning method.
Console.WriteLine("\n=== Level S Check ===");
Console.WriteLine("Level 0 (S): " + IsLevelS(0)); // True
Console.WriteLine("Level 1 (A): " + IsLevelS(1)); // False
}
}
This produces the following output:
dotnet script ReturnBasic.cs === Level Name Lookup === Level 0 -> S Level 1 -> A Level 2 -> B Level 3 -> C Level 4 -> D === Score Display === member_2's score: 1200 member_6 has no score. member_1's score: 8500 === Level S Check === Level 0 (S): True Level 1 (A): False
ReturnGuardClause.cs
using System;
class ReturnGuardClause {
// Checks eligibility for a member.
static string CheckEligibilityNested(string name, int level, int score) {
if (name != null && name != "") {
if (level <= 2) { // Level B or higher can participate.
if (score >= 500) { // Requires at least 500 score.
return name + " can participate.";
} else {
return name + " does not have enough score (" + score + ").";
}
} else {
return name + " does not have a high enough level (Level " + level + ").";
}
} else {
return "Name is empty.";
}
}
// Handling error cases first keeps nesting shallow.
static string CheckEligibility(string name, int level, int score) {
// Guard 1: validate name.
if (name == null || name == "") {
return "Name is empty.";
}
// Guard 2: validate level.
if (level > 2) {
return name + " does not have a high enough level (Level " + level + ").";
}
// Guard 3: validate score.
if (score < 500) {
return name + " does not have enough score (" + score + ").";
}
// Main logic reached only when all guard clauses pass.
return name + " can participate.";
}
static void Main() {
Console.WriteLine("=== Eligibility Check (nested) ===");
Console.WriteLine(CheckEligibilityNested("member_2", 1, 1200));
Console.WriteLine(CheckEligibilityNested("member_4", 3, 800));
Console.WriteLine(CheckEligibilityNested("member_3", 2, 200));
Console.WriteLine(CheckEligibilityNested("", 1, 1000));
Console.WriteLine("\n=== Eligibility Check (guard clause) ===");
// Confirm that the same inputs produce the same results.
Console.WriteLine(CheckEligibility("member_2", 1, 1200));
Console.WriteLine(CheckEligibility("member_4", 3, 800));
Console.WriteLine(CheckEligibility("member_3", 2, 200));
Console.WriteLine(CheckEligibility("", 1, 1000));
}
}
This produces the following output:
dotnet script ReturnGuardClause.cs === Eligibility Check (nested) === member_2 can participate. member_4 does not have a high enough level (Level 3). member_3 does not have enough score (200). Name is empty. === Eligibility Check (guard clause) === member_2 can participate. member_4 does not have a high enough level (Level 3). member_3 does not have enough score (200). Name is empty.
ReturnTuple.cs
using System;
class ReturnTuple {
static string GetName(string name) {
return name;
}
// Returns both name and level together.
static (string name, int level) GetMemberInfo(string name, int level) {
return (name, level); // Wrap multiple values in parentheses and return them.
}
// Returns detailed info (name, level, score) for a member.
static (string name, int level, int score) GetMemberDetail(
string name, int level, int score) {
return (name, level, score);
}
static (int total, double average) CalcTeamScore(int[] scores) {
int total = 0;
foreach (int e in scores) {
total += e;
}
double average = (double)total / scores.Length;
return (total, average); // Returns total and average together.
}
static void Main() {
// Use .fieldName to access each element.
Console.WriteLine("=== Member Info Lookup ===");
var info = GetMemberInfo("member_1", 0);
Console.WriteLine("Name: " + info.name + " Level: " + info.level);
// You can write: var (var1, var2) = Method()
Console.WriteLine("\n=== Receiving with Deconstruction ===");
var (name, level, score) = GetMemberDetail("member_5", 0, 7000);
Console.WriteLine("Name: " + name + " Level: " + level + " Score: " + score);
Console.WriteLine("\n=== Team Score Aggregation ===");
int[] teamScores = { 1200, 900, 3000, 8500 };
var (total, average) = CalcTeamScore(teamScores);
Console.WriteLine("Total score: " + total);
Console.WriteLine("Average score: " + average.ToString("F1")); // Display with 1 decimal place.
Console.WriteLine("\n=== Display Info for Multiple Members ===");
string[] names = { "member_2", "member_3", "member_4", "member_1" };
int[] levels = { 1, 2, 3, 0 };
for (int i = 0; i < names.Length; i++) {
var member = GetMemberInfo(names[i], levels[i]);
// Use member.name / member.level instead of names[i] and levels[i].
Console.WriteLine(member.name + " (Level: " +
(member.level == 0 ? "S" : "Level " + member.level) + ")");
}
}
}
This produces the following output:
dotnet script ReturnTuple.cs === Member Info Lookup === Name: member_1 Level: 0 === Receiving with Deconstruction === Name: member_5 Level: 0 Score: 7000 === Team Score Aggregation === Total score: 13600 Average score: 3400.0 === Display Info for Multiple Members === member_2 (Level: Level 1) member_3 (Level: Level 2) member_4 (Level: Level 3) member_1 (Level: S)
Common Mistakes
Missing return on some code paths
In methods whose return type is not void, every branch must reach a return statement. If you write return in only one branch of an if and forget the else side, the compiler reports error CS0161 ("not all code paths return a value").
static string GetLabel(int value) {
if (value > 0) {
return "positive";
}
// No return here — compile error.
}
static string GetLabel(int value) {
if (value > 0) {
return "positive";
}
return "zero or negative";
}
Returning a value from a void method
Writing return value; in a method declared as void causes a compile error. If you need to return a value, change the method's return type.
static void GetMessage() {
return "hello"; // CS0127: cannot return a value from a void method.
}
Overview
The return statement in C# immediately ends method execution and returns control to the caller. In methods whose return type is not void, a compile error occurs if any code path does not reach a return statement. The compiler detects missing return paths in switch and if / else branches, letting you catch mistakes early.
A guard clause (early return) is a pattern where invalid conditions are rejected at the top of the method. By placing null checks and range validations at the start, the rest of the method can assume valid inputs, keeping if / else nesting shallow. As shown in the eligibility check sample, the nested version and the guard clause version produce identical results, but the latter is easier to read.
Returning multiple values with a tuple is available in C# 7 and later. Naming the elements as in (Type1 Name1, Type2 Name2) lets you access them via .Name1, and you can also unpack them into individual variables using deconstruction: var (var1, var2) = Method(). Tuples are convenient when you need to return multiple values but a dedicated class would be overkill. In async methods, the value passed to return is automatically wrapped in Task<T> (see async / await). For returning different values per branch, see also switch statement / switch expression and Ternary / Null-Conditional Operators.
If you find any errors or copyright issues, please contact us.