Chapter 10

Improving Productivity with Named and Optional Parameters

IN THIS CHAPTER

Bullet Distinguishing between named and optional parameters

Bullet Using optional parameters

Bullet Declaring and using output parameters

Bullet 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.

Exploring Optional Parameters

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#.

Working with optional value parameters

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;
}

Warning And sometimes problems can be subtle, so use optional parameters carefully. For instance, say you have a base class and then derive a class that implements the base, like this:

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.

Avoiding optional reference types

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:

  • String
  • Object
  • Dynamic

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.

Looking at Named Parameters

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.

Using Alternative Methods to Return Values

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.

Output (out) parameters

Output 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.

Working with out variables

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 out variable using a traditional approach. However, consider the following example, which has just one out variable.

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().

Tip Of course, if your method returns just one output parameter, it's normally best to use a return value instead. This example uses just one so that you get a better idea of how the new technique works.

Remember A more interesting addition to C# 7.0 is that you can now use the var keyword (where the compiler infers the variable’s data type) with out parameters. For example, this call is perfectly acceptable in C# 7.0.

static void DisplayMyCalc()
{
MyCalc(out var p);
Console.WriteLine($"{nameof(p)} = {p}");
}

Returning values by reference

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;
}

Remember However, notice that you're returning an entire array now, instead of a single int value. The array is a reference type; the int is a value type. You can't return value types by using this technique. To make returning a value type possible, you must pass it in as a parameter, like this:

myInt = 1;
static ref int ReturnByReference(ref int myInt)
{
return ref myInt;
}

Dealing with null Parameters

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.130.201