Method Parameter And Reference Changes - C# 6 To C# 9 New Features - Day Two

 This article covers the changes done for method parameters and method references and focuses on “in”, “out”, “ref” and “readonly” keywords related changes in the newer version of C# i.e., from C# 6.0 to C# 8.0 and the upcoming version of C# 9.0*, which includes the introduction of the new parameter type with “in” keyword, inline “out” variable declaration, “ref return”, “ref local” and “ref readonly” and non-trailing named arguments.
 
This article is the second article of the series, and below is the link for my previous article. It is highly recommended to go through the previous article to have a better understanding.
In the previous article, I have covered 14 different syntaxes and concepts related to a null value and a null reference handling.
 
In this article, we are going to cover syntax changes and enhancement done for the “ref”, “out” and “in” parameters.
 
We already have the “ref” parameter and “out” parameter before C# 7.2 as well. In C# 7.2 “in” parameter has been introduced. Apart from that, a lot of new changes have been made for the above mentioned 3 types of parameters. Let us have a look at all those improvements one by one. 
 
Method Parameter and Reference Changes
 
Inline out variable declaration (declaring out variable inline in the method where it is being used).
 
Before C# 7.0, we need to declare an out variable before it is being passed as a parameter. But in C# 7.0 onwards, we do not need to declare an out variable before it is being used. We can declare it inline. Let’s us have a look at the below code snippet:
 
Let us take an example
 
Before C# 7.0 
  1. string s = "123";  
  2. int result;  
  3. int.TryParse(s, out result);  
 In C# 7.0 onwards 
  1. string s = "123";  
  2. int.TryParse(s, out int result);  
The visual studio gives us a hint for using the inline out the variable declaration. The following is a screenshot of the same.
 
Visual Studio hint for Inline variable declaration
 
Inline ‘out’ variable declaration is not limited to in-built C# library methods. We can use an inline out variable anywhere. Let take another example where we are going to create a method by ourselves.
 
Before C# 7.0
  1. static void Main(string[] args)  
  2.         {  
  3.             int sum, diff;  
  4.             int x = 10, y = 7;  
  5.             Calculate(x, y, out sum, out diff);  
  6.             Console.WriteLine($"sum: {sum} and difference: {diff}");  
  7.         }  
  8.   
  9. public static void Calculate(int x, int y, out int sum, out int diff)  
  10.         {  
  11.             sum = x + y;  
  12.             diff = x - y;  
  13.         }  
 In C# 7.0 and onwards 
  1. static void Main(string[] args)  
  2. {  
  3.     int x = 10, y = 7;  
  4.     Calculate(x, y, out int sum, out int diff);  
  5.     Console.WriteLine($"sum: {sum} and difference: {diff}");  
  6. }  
  7.   
  8. public static void Calculate(int x, int y, out int sum, out int diff)  
  9.         {  
  10.             sum = x + y;  
  11.             diff = x - y;  
  12.         }  
If you would like to explore more about the basics of the “ref” and “out” keyword, then you can go through another article. The following is a link for the same.
 

“ref” return (making the returning variable of a method as a storage reference variable).

 
“ref” return is a new feature introduced with C# 7.0, and it provides the capability to a method to return its result as a reference.
 
Example of “ref” return 
  1. //returning the result as a reference  
  2.         private ref int GetFirstOdd(int[] numArray)  
  3.         {  
  4.             for (int i = 0; i < numArray.Length - 1; i++)  
  5.             {  
  6.                 if (numArray[i] % 2 == 1)  
  7.                 {  
  8.                     //returning as reference  
  9.                     return ref numArray[i];   
  10.                 }  
  11.             }  
  12.             throw new Exception("not found");  
  13.         }  

“ref” local (making local variable of a method as a storage reference variable).

 
Example of “ref” local
  1. ref int firstOdd = ref p.GetFirstOdd(numArray);  
As we discussed earlier, a method can return as a reference, so we need a local variable as well with reference to store that value, which is known as “ref” local in C# 7.0 onwards.
 
To understand “ref” return and “ref” local in a better way, let us write a small C# code.
 
Following is a code to find the first odd number in an Integer array and multiply it by 2 and store it in the same array.
  1. static void Main(string[] args)  
  2.         {  
  3.             int[] numArray = { 2, 3, 4, 5, 6 };  
  4.   
  5.             for (int i = 0; i < numArray.Length-1; i++)  
  6.             {  
  7.                 if (numArray[i] % 2==1)  
  8.                 {  
  9.                     numArray[i] = numArray[i] * 2;  
  10.                     break;  
  11.                 }  
  12.             }  
  13.   
  14.             Console.WriteLine(string.Join(", ", numArray));  
  15.         }  
Output
 
2, 6, 4, 5, 6
 
So, we can see that we can update the first odd number successfully. But can we update it from another variable being used from outside as well?
 
Let us have a look at the below code snippet and try to achieve the same.
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             Program p = new Program();  
  6.             int[] numArray = { 2, 3, 4, 5, 6 };  
  7.   
  8.             var firstOdd = p.GetFirstOdd(numArray);  
  9.             firstOdd *= 2;  
  10.             Console.WriteLine(string.Join(", ", numArray));  
  11.         }  
  12.   
  13.         private int GetFirstOdd(int[] numArray)  
  14.         {  
  15.             for (int i = 0; i < numArray.Length - 1; i++)  
  16.             {  
  17.                 if (numArray[i] % 2 == 1)  
  18.                 {  
  19.                     return numArray[i];  
  20.                 }  
  21.             }  
  22.             throw new Exception("not found");  
  23.         }  
  24.     }  
Output
 
2, 3, 4, 5, 6
 
So we can see that we can update an array element by using its index, but from another variable, we cannot update because it returns the variable as a value type, and updating that variable does not impact the array.
 
Let’s try to achieve the same from the “ref” return and “ref” local feature of C# 7.0 
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             Program p = new Program();  
  6.             int[] numArray = { 2, 3, 4, 5, 6 };  
  7.   
  8.             //storing as reference  
  9.             ref int firstOdd = ref p.GetFirstOdd(numArray);  
  10.             Console.WriteLine($"Array before update :{string.Join("", numArray)}");  
  11.             firstOdd *= 2;  
  12.             Console.WriteLine($"Array after update  :{string.Join("", numArray)}");  
  13.   
  14.         }  
  15.   
  16.         //returning the result as reference  
  17.         private ref int GetFirstOdd(int[] numArray)  
  18.         {  
  19.             for (int i = 0; i < numArray.Length - 1; i++)  
  20.             {  
  21.                 if (numArray[i] % 2 == 1)  
  22.                 {  
  23.                     //returning as reference  
  24.                     return ref numArray[i];   
  25.                 }  
  26.             }  
  27.             throw new Exception("not found");  
  28.         }  
  29.     }  
Output
 
Array before update :2, 3, 4, 5, 6 
Array after update :2, 6, 4, 5, 6
 

“ref readonly” modifier on method returns.

 
“ref readonly” is a new modifier introduced with C# 7.2. “readonly” is not limited to method return. With struct type, it is being used very intensively. But we are focusing on methods changes in this article and may cover “readonly” for struct in upcoming articles.
 
So take the same example which we have used above and try to add a “ref readonly” modifier with the method signature. 
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             // Omitted for brevity  
  6.             ref int firstOdd = ref p.GetFirstOdd(numArray);  
  7.             // Omitted for brevity  
  8.         }    
  9.         private ref readonly int GetFirstOdd(int[] numArray)  
  10.         {  
  11.          // Omitted for brevity  
  12.         }  
  13.     }  
Below is the screenshot of the error message we are getting while trying to store it in a “ref” type variable.
 
ref readonly-error
 
To fix this replace the below line of code
  1. ref int firstOdd = ref p.GetFirstOdd(numArray);  
by 
  1. int firstOdd = p.GetFirstOdd(numArray);  
and now it will work completely as intended. Below is the complete code 
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             Program p = new Program();  
  6.             int[] numArray = { 2, 3, 4, 5, 6 };  
  7.   
  8.             //storing as reference  
  9.             int firstOdd = p.GetFirstOdd(numArray);  
  10.             Console.WriteLine($"Array before update :{string.Join("", numArray)}");  
  11.             firstOdd *= 2;  
  12.             Console.WriteLine($"Array after update  :{string.Join("", numArray)}");  
  13.   
  14.         }  
  15.   
  16.         //returning the result as reference  
  17.         private ref readonly int GetFirstOdd(int[] numArray)  
  18.         {  
  19.             for (int i = 0; i < numArray.Length - 1; i++)  
  20.             {  
  21.                 if (numArray[i] % 2 == 1)  
  22.                 {  
  23.                     //returning as reference  
  24.                     return ref numArray[i];  
  25.                 }  
  26.             }  
  27.             throw new Exception("not found");  
  28.         }  
  29.   
  30.     }  
Note
The “ref readonly” modifier is also known as “readonly references.” Following is a screenshot of error when trying to use this earlier version of C#, and due to that, it is giving an error where Visual is referring to it as ‘readonly references’.
 
readonly refrerence version warning
 

Reassign “ref local” variables of a method.

 
So far, we have seen how we can play with “ref” and “ref readonly”. In the previous examples, we can see that we can re-assign value for a “ref local” variable. But the re-assignment was not available and it was added in the later version of C# 7.x. However, now we can use it with any version of C# 7.x and onwards.
 
Following code snippet is an example of a “ref” local variable re-assignment. 
  1. //storing as reference  
  2. ref int firstOdd = ref p.GetFirstOdd(numArray);  
  3. Console.WriteLine($"Array before update :{string.Join("", numArray)}");  
  4. firstOdd *= 2;  
  5. Console.WriteLine($"Array after update  :{string.Join("", numArray)}");  
The feature “ref local” is also referred to as “byref locals and returns”. Following is a screenshot where we are using “ref return” and “ref local” and set language version C# 6.0, so it is giving the error. And we can see in the error that it is referring to this feature as “byref”.
 
readonly refrerence version warning
 
Note
  1. One of the most important things we should note here that when you are passing or returning a variable as a reference then reference of that object is passed, and thus, no new copy created. It will improve the performance of the application as we are not creating a new copy but using the same copy.
  2. In the previous example, we can see that we are changing the C# language version, but in a later version of Visual Studio 2019 and onwards, you cannot change the language version. It is linked with your framework, and to change the language version we need to change the framework version as well.
“in” modifier on a parameter (passing read-only references inside a method using “in” parameter).
 
So far, we have seen many examples of using the “ref” and “out” parameter. In C# 7.2 onwards, we can have an “in” parameter as well.
 
Example
  1. public static void CalculateTax(in Employee emp)  
  2.         {  
  3.             //do calculation --Omitted for brevity  
  4.         }  
Complete code
  1. class InParamExample  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             Employee employee = new Employee();  
  6.             CalculateTax(employee);  
  7.         }  
  8.         public static void CalculateTax(in Employee emp)  
  9.         {  
  10.             //do calculation --Omitted for brevity  
  11.         }  
  12.   
  13.         public class Employee  
  14.         {  
  15.             //Omitted for brevity  
  16.         }  
  17.     }  
The “in” modifier on a parameter is used to pass a variable as a reference like “ref” and “out” parameter. However, in this case, the passed variable is readonly and meant for input only. Thus we cannot modify the value of the parameter where it has been passed as the “in” parameter.
 

Why we need an “in” parameter?

 
To understand why we need an “in” parameter, take the same example which we have used above. 
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             Employee employee1 = new Employee { Id = 101, Salary = 5000 };  
  6.             Employee employee2 = new Employee { Id = 102, Salary = 6000 };  
  7.             decimal tax = CalculateTax(employee1);  
  8.             decimal emp1tax = CalculateTax(employee2);  
  9.   
  10.             Console.WriteLine($"Id: {employee1.Id} Salary: {employee1.Salary} and Tax: {CalculateTax(employee1)}");  
  11.             Console.WriteLine($"Id: {employee2.Id} Salary: {employee2.Salary} and Tax: {CalculateTax(employee2)}");  
  12.         }  
  13.         public static decimal CalculateTax(Employee emp)  
  14.         {  
  15.             decimal tax = 0;  
  16.             //do calculation  
  17.   
  18.             //a buggy line of code  
  19.             emp = new Employee { Id=502, Salary=2000};  
  20.   
  21.             tax = emp.Salary * 0.1m;  
  22.             return tax;  
  23.         }  
  24.   
  25.         public class Employee  
  26.         {  
  27.             public int Id { getset; }  
  28.             //public string Name { get; set; }  
  29.             public decimal Salary { getset; }  
  30.             //Omitted for brevity  
  31.         }  
  32.     }  
You can see what the buggy code has done. We asked to calculate the tax, but it is returning a fixed value 200 for tax.
 
buggy code output
 
Let us fix this using In parameter. Replace the code of CalculateTax() with the following code snippet. 
  1. public static decimal CalculateTax(in Employee emp)  
  2.         {  
  3.             decimal tax = 0;  
  4.             //do calculation  
  5.   
  6.             //a buggy line of code  
  7.             emp = new Employee { Id=502, Salary=2000};  
  8.   
  9.             tax = emp.Salary * 0.1m;  
  10.             return tax;  
  11.         }  

Error CS8331: Cannot assign to variable 'in Program.Employee' because it is a readonly variable

 
As we can see that as soon as we have provided the “in” parameter, it stopped working, and we need to fix the issue. If you are interested in exploring more about the “in” parameter, you can refer to my earlier articles.
Non-Trailing named arguments
 
Non-Trailing named arguments was a new method enhancement done with C# 7.2. Before C# 7.2, we cannot have non-trailing named arguments.
 
Before C# 7.2 below code is not allowed
  1. long result = Sum(10, b: 20, 30);  
 To understand in details have a look at the below code snippet
  1. static void Main()  
  2. {  
  3.     long result = Sum(10, b: 20);  
  4. }  
  5.   
  6. static long Sum(int a, int b, int c = defaultint d = default)  
  7. {  
  8.     return a = b + c + d;  
  9. }  
We can see that we can pass the 2nd parameter as named argument and we can run and compile this code successfully on C# 7.1.
 
Now add 3rd value like the below code snippet and re-compile the code in C# 7.1 
  1. long result = Sum(10, b: 20, 30);  
version warning-3
 
As we can see that as soon as we are providing the 3rd parameter the 2nd parameter i.e., “b” becomes a non-trailing parameter and so we cannot pass it named arguments till C# 7.1.
 
In C# 7.2 onwards, a named argument can be a non-trailing parameter. So the below code gets compiled successfully in C# 7.2 onwards.
  1. static void Main()  
  2.         {  
  3.             long result = Sum(10, b: 20, 30);  
  4.         }  
  5. static long Sum(int a, int b, int c = defaultint d = default)  
  6. {  
  7.     return a = b + c + d;  
  8. }  
But it does not mean that we can provide a named argument at any position without caring for the original sequence of the parameters. If the sequence is not correct, then the compiler may not be able to decide which value belongs to which parameter.
  1. //valid in C# 7.2 onwards  
  2. long result = Sum(10, b: 20, 30);  
  3.   
  4. //below is also valid in C# 4.0 onwards.  
  5. result = Sum(10, 20, d:40);  
  6.   
  7. //But below is not allowed in any version of C#  
  8. result = Sum(10, 20, d: 40, 30);  
It gives the compilation error.
 
Error CS8323: Named argument 'd' is used out-of-position but is followed by an unnamed argument.
 

Summary

 
In this article, we have covered the following topics
  1.  inline ‘out’ variable declaration 
  2.  'in’ modifier on a parameter 
  3.  ‘ref’ return 
  4.  ‘ref readonly’
  5. ‘ref local
  6. Reassign ‘ref local’
  7. Non-trailing named arguments


Similar Articles