Chapter 10
IN THIS CHAPTER
Distinguishing between named and optional parameters
Using optional parameters
Declaring and using output parameters
Implementing reference return types
Parameters, as you probably remember, are the inputs to methods. They’re the values that appear as part of the method’s signature. When the method returns a value (it doesn’t always do so), the parameters provide the data required to generate the output value. Sometimes, the return values are parameters (out
parameters), which confuses things.
In ancient versions of C# and most C-derived languages, parameters can't be optional (oddly enough, you find some examples of this ancient code lurking about online just waiting to make you feel hindered). Instead of making parameters optional, you are required to make a separate overload for every version of the method you expect your users to need. This pattern works well, but there are some problems that are explored in this chapter.
C# 4.0 and above have optional parameters. Optional parameters are parameters that have a default value right in the method signature. It's the same control-versus-productivity issue that Book 3, Chapter 6 shows you about the dynamic type. Optional parameters give you just enough rope to hang yourself. A programmer can easily make mistakes.
Optional parameters depend on having a default value set in order to be optional. For instance, if you're searching for a phone number by name and city, you can default the city name to your city, making the parameter optional.
public static string searchForPhoneNumber(string name, string city = "Columbus") {…}
The following sections discuss how to work with optional parameters in C#.
Here is the reason that optional parameters are so amazing. Consider the following code for the addit()
method. It's silly, but it illustrates the realities of multiple overloads. So, previously you had this:
public static int addit(int z, int y)
{
return z + y;
}
public static int addit(int z, int y, int x)
{
return z + y + x;
}
public static int addit(int z, int y, int x, int w)
{
return z + y + x + w;
}
public static int addit(int z, int y, int x, int w, int v)
{
return z + y + x + w + v;
}
With optional parameters, you now have this:
public static int addit(int z, int y, int x = 0, int w = 0, int v = 0)
{
return z + y + x + w + v;
}
If you need to add two numbers, you can do it easily.
int answer = addit(10, 4),
If you need to add four numbers, you have no problems, either.
int answer = addit(10, 4, 5, 12);
So why are optional parameters dangerous? Because sometimes default values can have unintended consequences. For instance, you don't want to make a divideit()
method and set the default parameters to 0
. Someone could call it and get a hard to debug division by zero error. Just as you wouldn't set the divideit()
method parameters to 0
, setting the optional values in addit()
to 1
would be bad. This would mean that the each optional value would automatically add 1 to the sum as shown below.
public static int addit(int z, int y, int x = 1, int w = 1, int v = 1)
{
// You CLEARLY don't want this
return z + y + x + w + v;
}
public abstract class Base
{
public virtual void SomeFunction(int x = 0)
{…}
}
public sealed class Derived : Base
{
public override void SomeFunction(int x = 1)
{…}
}
What happens if you declare a new instance?
Base ex1 = new Base();
ex1.SomeFunction(); // SomeFunction (0)
Base ex2 = new Derived();
ex2.SomeFunction(); // SomeFunction (0)
Derived ex3 = new Derived();
ex3.SomeFunction(); // SomeFunction (1)
What happened here? Depending on how you implement the classes, the default value for the optional parameter is set differently. The first example, ex1
, is an instantiation of Base
, and the default optional parameter is 0
. In the second example, ex2
is cast to a type of Derived
(which is legal because Derived
is a subclass of Base
), and the default value is also 0
. However, in the third example, Derived
is instantiated directly, and the default value is 1
. This isn't expected behavior. No matter how you slice it, it's a gotcha and something to watch out for.
A reference type, as Book 1 discusses, types a variable that stores a reference to actual data, instead of the data itself. Reference types are usually referred to as objects. New reference types are implemented with
class
interface
delegate
These need to be built before you use them; class itself isn't a reference type, but the Calendar
class is. There are three built-in reference types in the .NET Framework:
You can pass a reference type into a method just as you can pass a static type. It is still considered a parameter. You still use it inside the method as you do with any other variable.
But can reference types be passed, as value types can? Give it a try. For instance, if you had a Schedule
method for your Calendar
class, you could pass in the CourseId
, or you could pass in the whole Course
. It all depends on how you structure the application.
public class Course
{
public int CourseId;
public string Name;
public void Course(int id, string name)
{
CourseId = id;
Name = name;
}
}
public class Calendar
{
public static void Schedule(int courseId)
{
}
public static void Schedule(Course course)
{
// Something interesting happens here
}
}
In this example, you have an overloaded method for Schedule
— one that accepts a CourseId
and one that accepts a Course
reference type. The second is a reference type, because Course
is a class, rather than a static type, like the int
of the CourseId
.
What if you want the second Schedule
method to support an optional Course
parameter? Say, if you want it to create a new Course()
by default, you omit the parameter. This would be similar to setting a static integer to 0
or whatever, wouldn't it?
public static void Schedule(Course course = New Course())
{
// Implementation here
}
This isn't allowed, however. Visual Studio allows optional parameters only on static types, and the compiler tells you so. (There are exceptions to this rule that aren't covered in the book, such as using nullable reference types in C# 8.0 or above as described at https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/nullable-reference-types
.) If you want to do this, you have to accept the CourseId
in the Schedule
method and construct a new Course
in the body of the event.
Hand in hand with the concept of optional parameters are named parameters. If you have more than one default parameter, you need a way to tell the compiler which parameter you're supplying! For example, look at the addit()
method earlier in this chapter, after optional parameters are implemented:
public static int addit(int z, int y, int x = 0, int w = 0, int v = 0)
{
return z + y + x + w + v;
}
Clearly, the order of the parameters doesn't matter in this implementation, but if this were in a class library, you might not know that the order of the parameters is a non-issue! How would you tell the compiler to skip x
and w
if you want to supply v
? With named parameters, you can say
int answer = addit(z:3, y:7, v:4);
The non-optional parameters don't have to be named; the position is assumed because they're required anyway. Nonetheless, naming them is good practice. If you skip naming them, you have this instead:
int answer = addit(3, 7, v:4);
You have to admit that this is a little harder to read. One would have to go back to the method signature to figure out what is happening. C# 7.2 does provide an additional wrinkle. Normally, you can’t have any positional arguments after using named arguments. However, starting with C# 7.2, you can do this:
int answer = addit(z:3, 7, v:4);
The 7
still relates to y
because it appears in the correct position. However, now your code is exceptionally hard to read.
Normally, you return values to the user by using the return
keyword followed by the value. Some situations require an alternative method of returning the value. For example, if you're working with the Win32 API, you often need to use a parameter, rather than an actual return value, because of the way that Microsoft wrote the original C/C++ code. You may find other situations where you need to return multiple values and don’t want to create a struct
, list
, or other object to return them. The following sections talk about these alternative methods to return values.
out
) parametersOutput parameters are parameters in the method signature that actually change the value of the variable that is passed into them by the user. The parameter references the location of the original variable, rather than create a working copy. Output parameters are declared in a method signature with the out
keyword. You can have as many as you like (well, within reason), although if you use more than a few, you probably should use something else (a generic list, maybe?). An output parameter might look like this in a method declaration:
public static void Schedule(int courseId, out string name,
out DateTime scheduledTime)
{
name = "something";
scheduledTime = DateTime.Now;
}
Following the rules, you should be able to make one of these parameters optional by presetting a value. Unlike reference parameters (described in the “Returning values by reference” section of this chapter), it makes sense that output parameters don't support default values. The output parameter is exactly that — output, and setting the value should happen inside the method body. Because output parameters aren't expecting a value coming in anyway, it doesn't benefit the programmer to have default values.
C# 7.0 changes the way return values work. You can now work with out
variables in new ways. The “Output (out) parameters” section of the chapter discusses the common methods of working with the
variable using a traditional approach. However, consider the following example, which has just one out
variable. out
static void MyCalc(out int x)
{
x = 2 + 2;
}
In this case, you can use a shorter way than shown in the previous section to obtain a result from MyCalc()
:
static void DisplayMyCalc()
{
MyCalc(out int p);
Console.WriteLine($"{nameof(p)} = {p}");
}
The output is a value of 4
because you set x
to 4
within MyCalc()
and x
is returned as a changed value to the caller. However, you don't need to declare p
before using it. The declaration occurs as part of the call to MyCalc()
.
static void DisplayMyCalc()
{
MyCalc(out var p);
Console.WriteLine($"{nameof(p)} = {p}");
}
There may be a situation where you want to return a variable, rather than a value, to the caller. For example, returning a variable allows the caller to treat the value it contains as either a reference or a value. Using return by reference can become a little complicated though, so you need to understand all of the rules found at https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/ref-returns
. You can return values by reference in older versions of C#. However, you have to create your code carefully to do it, as shown here:
int[] arrayData = { 1, 2 };
static ref int ReturnByReference()
{
ref int x = ref arrayData[0];
return ref x;
}
In C# 7.0 and above, you can reduce the code used to perform this task to this:
int[] arrayData = { 1, 2 };
static ref int ReturnByReference()
{
return ref arrayData;
}
myInt = 1;
static ref int ReturnByReference(ref int myInt)
{
return ref myInt;
}
Receiving a null
value for a method parameter is problematic unless you provide some means of dealing with it. In the past, you needed to provide your own null argument checking for a method call, such as:
static void SayHello(String Name)
{
if (null == Name)
throw new ArgumentNullException("name");
else
Console.WriteLine($"Hello {Name}!");
}
There are two problems here. First, you might forget to add the required null
checking code. Second, you're essentially writing the same code over and over again, which is quite annoying, and no one wants to be annoyed. C# 10.0 has an answer to the problem in the form of null
parameter checking using the !!
operator. So, this example code looks like this now:
static void SayHello(String Name!!)
{
Console.WriteLine($"Hello {Name}!");
}
The result is that you get the same amount of work done, with half as much labor. When SayHello()
receives a null
input, it automatically throws an ArgumentNullException
.