Understanding Methods
C# 6 introduces expression-bodied members that shorten the syntax for single-line methods.
static int Add(int x, int y) => x + y;
This is what is commonly referred to as "syntactic sugar", meaning that the generated IL is no different.
Understanding Local Functions (New 7.0, Updated 9.0)
A local function is a function declared inside another function, must be private, with C# 8.0 can be static. (and does not support overloading local function do support nesting) a local function can have a local function declared inside it.
static int Add(int x, int y)
{
// Do some validation here.
return x + y;
}
C# 9.0 updated local functions to allow for adding attributes to a local function, its parameters, and its type parameters.
#nullable enable
private static void Process(string?[] lines, string mark)
{
foreach (var line in lines)
{
if (IsValid(line))
{
//
}
}
bool IsValid([NotNullWhen(true)] string? line)
{
return !string.IsNullOrEmpty(line) && line.Length >= mark.Length;
}
}
Understanding Method Parameters
Method parameters are used to pass data into a method call.
Understanding Method Parameter Modifiers
The default way a parameter is sent into a function is by value. If you don't mark an argument with a parameter modifier, a copy of the data is passed into the function.
C# Parameter Modifiers
- Parameter Modifier (None) : If a value type parameter is not marked with a modifier, it is assumed to be passed by value, meaning the called method receives a copy of the original data. Reference types without a modifier are passed in by reference.
- out : Parameters must be assigned by the method being called, and, therefore, are passed by reference. If the called method fails to assign output parameters, you are issued a compiler error.
- ref : The value is initally assigned by the caller and may be optionally modified by the called method (as the data is also passed by reference). No compiler error is generated if the called method fails to assign a ref parameter.
- new in C# 7.2, the in modifier indicates that a ref parameter is read-only by the called method.
- params : This parameter modifier allows you to send in a variable number of arguments as a single logical parameter. A method can have only a single params modifier, and it must be the final parameter of the method.
Understanding the Default Parameter-Passing Behavior
When a parameter does not have a modifier, the behavior for value types is to pass in the parameter by value and for reference types is to pass in the parameter by reference.
// Value type arguments are passed by value by default.
static int Add(int x, int y)
{
int ans = x + y;
// Caller will not see these changes as you are modifying a copy of the original data.
x = 10000;
y = 88888;
return ans;
}
The Default Behavior for Reference Types
The default way a reference type parameter is sent into a function is by reference for its properties, but by value for itself.
Note: When a string parameter does not have a modifier, it is passed in by value.
Using the out Modifier (Updated 7.0)
Methods that have been defined to take output parameters (via the out keyword) are under obligation to assign them to an appropriate value before exiting the method scope (if you fail to do so, you will receive compiler errors).
// Output parameters must be assigned by the called method.
static void AddUsingOutParam(int x, int y, out int ans)
{
ans = x + y;
}
Caling a method with output parameters also required the use of the "out" modifier.
// 1
int ans;
AddUsingOutParam(90, 90, out ans);
// 2
AddUsingOutParam(90, 90, out int ans_2);
Console.WriteLine(ans_2);
The C# out modifier does serve a useful purpose; it allows the caller to obtain multiple outputs from a single method invocation:
// Returning multiple output parameters.
static void FillTheseValues(out int a, out string b, out bool c)
{
a = 9;
b = "Enjoy";
c = true;
}
FillTheseValues(out int a, out string b, out bool c);
Console.WriteLine(a + " " + b + " " + c);
Note : Always remember that a method defines output parameters must assign the parameter to a valid values before exiting the method scope.
static void ThisWontCompile(out int a ){
Console.WriteLine("Error! Forgot to assign output arg");
}
Discarding out Parameter (New 7.0)
If you don't care about the value of an out parameter, you can use a discard as a placeholder. Discards are temporary, dummy variables that are intentionally unused.
They are unassigned; do not have a value, and might not even allocate any memory.
// This only gets the value for; a,and ignores the other 2 parameters.
FillTheseValues(out int a,out _,out _);
Using the ref Modifier
Reference parameters are necessary when you want to allow a method to operate on various data points declared in the caller's scope.
Output vs Reference
- Output parameters do not need to be initialized before they are passed to the method. The reason for this is that the method must assign output parameters before exiting.
- Reference parameters must be initialized before they are passed to the method. The reason for this is that you are passing a refence variable. If you do not assign it to an initial value that would be the equivalent of an operating on an unassigned local variable.
// Reference strings
public static void SwapStrings(ref string s1, ref string s2)
{
string tempStr = s1;
s1 = s2;
s2 = tempStr;
}
string str1 = "Flip";
string str2 = "Flop";
Console.WriteLine(str1 + " " + str2);
SwapStrings(ref str1, ref str2);
Console.WriteLine(str1 + " " + str2);
Using the in Modifier (New 7.0)
The in modifier passes a value by reference (for both value and reference types) and prevents the called method from modifying the values.
When value types are passed by value, they are copied (internally) by the called method.
If the object is large (such as a large struct), the extra overhead of making a copy for local use can be significant.
Also, even when reference types are passed without a modifier, they can be modified by the called method. Both issues can be resolved using the "in" modifier.
static int AddLoadOnly(in int x, in int y)
{
// Error! Cannot assign to variable 'in int' because it is a read-only variable.
x = 10000;
y = 88888;
int ans = x + y;
return ans;
}
Using the Params Modifier
C# supports the use of "parameter arrays" using the params keyword. The params keyword allows you to pass into a method a variable number of identically typed parameters (or classes related by inheritance) as a single logical parameter.
As well, arguments marked with the params keyword can be processed if the caller sends in a strongly types array of a comma-delimited list of items.
static double CalculatedAverage(params double[] values)
{
double sum = 0;
if (values.Length > 0)
{
for (int i = 0; i < values.Length; i++)
{
sum += values[i];
}
}
return (sum / values.Length);
}
Note : To avoid any ambiguity, C# demands a method support only a single "params" argument, which must be the final argument in the parameter list.
Defining Optional Parameters
C# allows you to create methods that can take "optional arguments". This technique allows the caller to invoke a single method while omitting arguments deemed unnecessary, provided the caller is happy with the specified defaults.
// owner is an optional parameter
static void EnterLogData(string message, string owner = "Programmer")
{
Console.WriteLine(owner + " " + message);
}
EnterLogData("Oh no!");
EnterLogData("Oh no!", "CFO");
One important thing to be aware of is that the value assigned to an optional parameter must be known at compile time and cannot be resolved.
// Error! Now property of DateTime class is resolved at runtime, not compile time.
static void EnterLogData(string message, DateTime timeStamp = DateTime.Now) { }
Note: To avoid ambiguity, optional parameters must always be placed at the end of a method signature. It is a compiler error to have optional parameters listed before non-optional parameters.
Using Named Arguments (Updated 7.0)
Named arguments allow you to invoke a method by specifying parameter values in any order you choose.
static void DisplayFancyMessage(ConsoleColor textColor, ConsoleColor backgroundColor, string message)
{
ConsoleColor oldTextColor = ConsoleColor.Black;
ConsoleColor oldBackgroundColor = ConsoleColor.Yellow;
Console.ForegroundColor = textColor;
Console.BackgroundColor = backgroundColor;
Console.WriteLine(message);
Console.ForegroundColor = oldTextColor;
Console.BackgroundColor = oldBackgroundColor;
}
Note: Just because you can mix and match named arguments with positional arguments in C# 7.2 and later.
// This is OK! as positional args are listed before named args.
DisplayFancyMessage(ConsoleColor.Blue,message:"Testing...",backgroundColor: ConsoleColor.White);
// This is OK! All arguments are in the correct order!
DisplayFancyMessage(textColor: ConsoleColor.White,backgroundColor: ConsoleColor.Blue,"Testing...");
// This is an ERROR! as positional args are listed after named args.
DisplayFancyMessage(message:"Testing...",backgroundColor: ConsoleColor.White,ConsoleColor.Blue);
Understanding Method Overloading
When you define a set of identically named methods that differ by the number (or type) of parameters, the method in question is said to be overloaded.
Method signature = method name + parameter list (count, type, order)
public static class AddOperations
{
public static int Add(int x, int y)
{
return x + y;
}
public static double Add(double x, double y)
{
return x + y;
}
public static long Add(long x, long y)
{
return x + y;
}
}
Console.WriteLine(AddOperations.Add(10, 10));
Console.WriteLine(AddOperations.Add(900_000, 900_000));
Console.WriteLine(AddOperations.Add(4.3, 4.3));
Finally, in,ref, and out are not considered as part of the signature for method overloading when more than one modifier is used. These will throw a compiler error.
// ERROR!
static int Add(ref int x);
static int Add(out int x);
However, if only one method uses in,ref,or our, the compoiler can distinguish the signatures. So this is allowed;
// This is allowed:
static int Add(ref int x) {/**/}
static int Add(int x) {/**/}
Checking Parameters for Null
If a method parameter is nullable (e.g, a reference type- like string) and required by the method body, it is considered a good programming practice to check that the parameter is not null before using it. If it is null, the method should throw an ArgumentNullException.
In C# 10, the ArgumentNullException has an extension method to do this in one line of code.
static void EnterLogData(string message, string owner = "DotNetGuard")
{
if (message == null)
{
ArgumentNullException.ThrowIfNull(message);
//throw new ArgumentNullException(message);
}
Console.WriteLine(message);
}