In C# programs, methods return only one value. A return
-statement is a jump statement. It transfers control unconditionally to the endpoint of the call site in the stack frame.
When a method is declared void
, no return value is included after "return." Other methods must return the correct value.
As a jump statement, return affects control flow. Control flow models the path of the execution engine when it manipulates evaluation stacks in methods.
MethodA
loads the constant 100 and returns that value to the call site. MethodA
is small so it is often inlined.MethodB
accepts a parameter of type boolean. The logic contained in the method is not returned, just the value of the evaluation.using System; class Program { static void Main() { // Call four methods and store their return values. int value1 = MethodA(); string value2 = MethodB(true); string value3 = MethodB(false); int value4 = MethodC(5, 2); // Display the results. Console.WriteLine(value1); Console.WriteLine(value2); Console.WriteLine(value3); Console.WriteLine(value4); // Invoke a void method. MethodD(); } static int MethodA() { // This method returns a constant integer. return 100; } static string MethodB(bool flag) { // This method returns a string reference based on the flag. return flag ? "cat" : "dog"; } static int MethodC(int operand1, int operand2) { // This method returns an integer based on an expression. return (operand1 * 10) + operand2; } static void MethodD() { // This method uses a return statement with no expression (void). Console.WriteLine("MethodD executed"); return; } }100 cat dog 52 MethodD executed
Some methods that return values do not use a return statement. The C# language supports expression-bodied methods.
using System; class Program { // An expression-bodied method. static string FormatFancy(string name) => ("Name: " + name.ToUpper()); static void Main(string[] args) { // Call the FormatFancy method. string result = FormatFancy("sam"); Console.WriteLine(result); } }Name: SAM
A function can be declared as a local function. It has access to the locals within the enclosing scope—it can read and write to them.
CreateString
. It changes the value of "multiplier" on each call.CreateString
method returns a string
created with string
interpolation syntax.class
is more standard and will be easier for others to use.using System; class Program { static void Main() { int multiplier = 10; // This is a local function. // ... It can access and change local variables in the surrounding scope. string CreateString(int value) { // Use string interpolation. // ... Modify multiplier after accessing its value. return $"STRING HAS VALUE: {value * multiplier++}"; } // Call the local function. Console.WriteLine(CreateString(5)); Console.WriteLine(CreateString(5)); } }STRING HAS VALUE: 50 STRING HAS VALUE: 55
In algorithms we often have methods that return a single value. It is tempting to use a ref
or out parameter in C# here.
return
-statement is faster. If we reorder the test so that Method2
precedes Method1
, the result is similar.using System; using System.Diagnostics; class Program { const int _max = 1000000; static void Main() { int temp; Method1("", out temp); Method2(""); // Version 1: use out return parameter. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { int result; Method1("cat", out result); if (result != 6) { throw new Exception(); } } s1.Stop(); // Version 2: use single return value. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { int result = Method2("cat"); if (result != 6) { throw new Exception(); } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } static void Method1(string test, out int result) { // Return value as an out parameter. result = 0; for (int i = 0; i < test.Length; i++) { result += 2; } } static int Method2(string test) { // Return value with return statement. int result = 0; for (int i = 0; i < test.Length; i++) { result += 2; } return result; } }2.03 ns 1.25 ns 2.08 ns 1.25 ns 2.03 ns 1.25 ns Averages: 2.05 ns Method, out 1.25 ns Method, return
This keyword specifies that no specific value is returned from a method. We must use "void
" in the method signature—this word was inherited from older C-like languages.
You could specify if
-statements to compute return values, but this approach is more verbose. Expressions can instead be used to condense high-level code.
Many programs return string
references of either constant form or newly-constructed strings. String
is a reference type in the language that is special-cased by the runtime.
string
type is pushed to the stack and it is only 4 or 8 bytes on computers.string
data is never copied when you assign a variable to the result of the method that returns a string
.This is the IL return instruction. Ret can be executed when there is one or zero items in the evaluation stack. The evaluation stack contains varying types of elements.
We can return a ref
to a struct
or array element. This allows the returned value to be modified in a single statement. These changes are reflected in the current scope.
It is common to return values in methods. We discussed the concepts of "reachability" and endpoints, and how the execution engine processes ret instructions.