If you’re a seasoned programmer you’ll know that the c# return statement is used to exit a method, optionally passing a value (a return parameter) back to the calling function.
But since you’re here, I’m guessing you’re new enough to C # (or programming in general) to benefit from a deeper dive into what a function is, what we mean by a returning from a function and how we go about returning parameters. So, let’s get into it!
Contents
Example 1 – Simple Return
public void ReturnExample() { Console.WriteLine("Hello World"); return; }
This example function just writes “Hello World” to the console then exits, but the interesting part is the return statement, this calls an end to the function and would be where a value could be returned (see returning a value example below). This method is declared as returning void, i.e. it doesn’t return anything, so we put nothing after the return statement.
Example 2 – Implied Return
The docs tell us that “If the method is a void
type, the return
statement can be omitted.”; this means that we can leave a function (i.e. return from a function) without explicitly using the return statement, but only in a function that returns void (i.e. doesn’t return anything).
public void ImpliedReturnExample() { Console.WriteLine("Hello World"); }
Example 3 – Returning a value
public int GetRandomNumber() { return 4; // Chosen by fair dice roll }
Besides the blatant homage to xkcd, this example shows a function which is called GetRandomNumber and we declare that it will return an int. The body of the function (the bit between the to curly braces) is a single line which return an integer, the integer ‘4’ to be exact.
What happens if you declare a function as returning an int, but return something else, like a string? or don’t return anything at all? Either way, you’ll get a compile time error: something like “Cannot implicitly convert type ‘string’ to ‘int'” or “An object of a type convertible to ‘int’ is required” respectively.
Using a returned value
What do we mean by returning a value? It means that the code that calls the function receives a value back when it calls itm and can make use of that value. This allows us to subdivide our code into re-useable blocks (functions) and to use the results of those blocks.
public int GetRandomNumber() { return 4; // Chosen by fair dice roll } public void PrintRandomNumber() { Console.WriteLine($"Today's random number is: {this.GetRandomNumber()}"); } // Today's random number is: 4
Example 4 – Early Return
So far, all of our examples functions have returned at the end, but there’s not reason you can’t return earlier in the function:
public void EarlyReturnExample() { Console.WriteLine("Hello World"); return; Console.WriteLine("This will not be written"); } // This code outputs: // Hello World
As you can see from the output of this example, the function ends when it hits the return statement, meaning that the second Console.WriteLine statement is not hit, and the phrase “This will not be written” isn’t written to the console.
This is a fairly contrived example, but this pattern can come in very useful if you need to stop processing early, e.g.:
public void PrintOddOrEven(int a) { if (a%2 == 0) { Console.WriteLine($"{a} is even"); return; } Console.WriteLine($"{a} is odd"); } // Sample output: // 2 is even // 3 is odd
This function writes the value passed in, followed either by “is even” or “is odd” depending on the value passed in. The interesting use of the return statement is that, if the value is even, we use a return statement to exit form the function early. This means we can omit the else clause from our if statement, safe in the knowledge that we’ll never print both “is even” and “is odd” for the same value. Choosing to return early instead of using an else statement can make your code easier to read by reducing the amount of indentation in the rest of your code.
Unreachable Code Detected
This is a compile time warning and it means that the compiler has found some code that is unreachable, there is no way that the code could be executed. This is usually caused by an early return statement, for example:
public void PrintRandomNumber() { Console.WriteLine($"Today's random number is: "); return; Console.WriteLine(4); } // Throws a compile time warning // Unreachable code detected // When run it prints: // Today's random number is:
Usually the issue is a lot more subtle that this. If you do ever get this warning it’s always worth digging in and finding out the problem, it could save you hours of debugging later.
Return Multiple Values
In C# a method/function can either one value or no values, it can’t return two or more values. However, there’s nothing in the rules to say that the value returned can’t itself be a group or list of things. Returning a tuple of values is the closest thing I know of to returning multiple values from a function in C#.
Returning a Tuple
My favourite way to return multiple things from a single function is to return them as a tuple. Tuples are a relatively new feature in C#, but are well worth learning about for situations such as these:
public (double, double) GetBothSquareRoots(int x) { var a = Math.Sqrt(x); var b = 0 - a; return (a, b); } // Example return values: // 9 -> 3, -3 // 100 -> 10, -10
Returning multiple values as a tuple has the advantage (over an array, or list) that the items do not need to be the same type:
public (string, System.Drawing.Color) MyFavouriteColour() { return ("Forest Green", System.Drawing.Color.ForestGreen); }
This is a contrived example (as you can get the name from the Color object) but there have been plenty of occasions where being able to return two or more things of different types from the same function has gotten me out of a jam.
That said, this counts as a “code smell”, it often suggests you’re doing something wrong. either that you’re methods are not specific enough or that you should be passing around a class rather than a collection of objects. That said, it sometimes is appropriate to return a tuple, but consider getting someone with experience to review your code to make sure you’re not doing anything daft.
Returning a List (or IEnumerable)
public IEnumberable<int> GetPositiveNumbersBelow(int a) { var list = new List<int>(); for (var i = 1; i < a; i++) { list.Add(i); } return list; } // example returned values // 3 -> [1,2] // 5 -> [1,2,3,4]
Yield Return
The above example (Returning a List) brings us nicely on to yield return. This lets us return a list (technically and IEnumerable) of objects, but with a much cleaner syntax, by yield returning each list item individually. This means we don’t have to declare a list variable and add to it, making the example much cleaner:
public IEnumerable<int> GetPositiveNumbersBelowAgain(int a) { for (var i = 1; i < a; i++) { yield return i; } } // Example return values: // 4 -> [1,2,3] // 6 -> [1,2,3,4,5]
I personally, love how neat the yield return version of this example is, and I try and use a yield return whenever it makes sense. Some IDEs like Visual Studio 2019 will prompt you to use yield return where appropriate.
Other similar statements
C# Break – leaving a loop
In the same way that a return statement can be used to leave.a method/function, we can use a break statement to leave a loop, such as a while loop:
public IEnumerable<int> DoubleUntil100(int x) { while(true) { x *= 2; if (x >= 100) { break; } yield return x; } } // Sample return values // 3 -> [6,12,24,48,96] // 5 -> [10,20,40,80]
This is a contrived example because we could have put our condition into the while() loop, but I hope you can see that the break statement causes us to leave the while loop when it gets executed. Unlike a return statement, we can’t include a value after a break.
C # Continue – finishing one iteration of a loop
What if you don’t want to exit a loop completely, but you do want to stop processing and move on to the next cycle in the loop? That’s where the continue statement comes in.
public IEnumerable<int> DoubleUntil100Excluding40(int x) { while(x<50) { x *= 2; if (x == 40) { continue; } yield return x; } } // Sample return values // 3 -> [6,12,24,48,96] // 5 -> [10,20,80]
In this example, hitting 40 doesn’t cause the while loop to stop running, but it does skip the rest of that iteration, meaning the yield return never runs for 40 and so 40 is never included in the output. If you’re not familiar with yield return, it might be worth re-reading that section above.
What does “Not all code paths return a value” mean?
This is a topic that deserves it’s own post, but for now I’ll explain that when a function is declared as returning a value, all paths through that code must return a value. If this rule is violated, you’ll get a compile time error:
public int Nothing() { } // Compile time error: // 'Program.Nothing(int)': not all code paths return a value
That was a pretty basic example (the function is empty, it’s clearly missing a return statement. But let’s take a look at a more involved example:
public int Collatz(int x) { if (x == 1) { return 1; } else if (x%2 == 0) { return Collatz(x/2); } } // Compile time error: // 'Program.Collatz(int)': not all code paths return a value
This program calls itself recursively if the number passed in is even, but if the number is not even – what happens? execution continues to the end of the function, the ‘}’ without returning anything. This is a path through the code that doesn’t return a value, which is not allowed for a function that is declared as returning an int, so we get a compile time error.
How do we fix it? It’s necessary to make sure that all paths through the function result in a value being returned:
public int Collatz(int x) { if (x == 1) { return x; } else if (x%2 == 0) { return Collatz(x/2); } else { return Collatz(3*x +1); } } // example return values: // 3 -> 1 // 5 -> 1 // 42 -> 1
It’s worth noting that this doesn’t make sure your program will always return a value. In fact, the Collatz conjecture (that every positive integer plugged into the above recursive function will always end up returning 1) is, as yet, unproven; so it’s conceivable to enter a value that ends up with the program never stopping (or more likely overflowing). What the compile time check is doing is ensuring there are no obvious gaps in your code where you’ve forgotten to return a value, it leaves the rest up to you!
Conclusion
The return statement is an integral part of the C# programming language, we’ve seen to how to use it to leave a function, when it can be omitted, and how to use it to return values, how to return multiple values, what yield return does and when it’s useful, and much more besides. I hope you’ve enjoyed this deep dive into statement that most people take for granted, and I hope you learned something along the way.
If you want a whole post on “Not all code paths return a value”, if something is still not clear to you, or you just want to show me some love, let me know in the comments!