Chapter 2. Improving Productivity with Named and Optional Parameters

In This Chapter

  • Distinguishing between named and optional parameters

  • Using optional parameters

  • Implementing reference types

  • Declaring Output parameters

Parameters, as you probably remember, are the inputs to methods. They are the values that you put in so that you get a return value. Sometimes, the return values are parameters, too, which confuses things.

In the world of C# and most C-derived languages, parameters can't be optional. Instead of making parameters optional, you are just expected to make a separate overload for every version of the method that you expect your users to need.

This pattern works fairly well, but there are still a lot of problems. Many VB programmers point to the flexible parameterization as a strong reason to use VB over C#.

C# 4.0 has optional parameters. Optional parameters are parameters that have a default value right in the method signature — just like the VB.NET implementation. This is one more step toward language parity, and again in the name of COM programming.

It's the same control versus productivity issue that Chapter 1 shows us about the dynamic type. Optional parameters give you just enough rope to hang yourself. A programmer can make mistakes just as easily as he can help himself.

Optional Parameters

Optional parameters depend on having a default value set in order to be optional. For instance, if you are 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") {...}

In C# 3.0, you implement this with two overloaded implementations of the search method. One of them includes the name and the city as parameters. The second only has the name as a parameter. It sets the city in the body of the method and calls the first method. The code looks like this:

public static string searchForPhoneNumber(string name, string
   city) {...}
public static string searchForPhoneNumber(string name) {
   string city = "Columbus";
   return searchForPhoneNumber(name, city);
}

The canonical example of this is the addit method. It's silly, but it illustrates the realities of multiple overloads. So, before we 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, we 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 we need to add two numbers, we can do it easily.

int answer = addit(10, 4),

If we need to add four numbers, we 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 default the parameters to 0. Someone could call it and get an undebuggable division by zero error. Setting the optional values in additall to 1 inside the method body would be bad.

public static int addit(int z, int y, int x = 0, int w = 0,
   int v = 1)
{
   //You CLEARLY don't want this
   return z + y + x + w + v;
}

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

public abstract class Base
{
    public virtual void SomeFunction(int x = 0)
    {...}
}

public sealed class Derived
{
    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 (0)

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 implementation of Base, and the default optional parameter is 0. In the second example, ex2 is cast to a type of Derived (which is legal, since 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 is not particularly expected behavior, though I have to admit that I am not sure WHAT expected behavior is in a case like this. No matter how you slice it, it's a gotcha and something to watch out for.

Reference types

A reference type, as Book 1 discusses, types a variable that stores reference to actual data, instead of the data itself. Reference types are usually referred to as objects, though this is a little inaccurate since everything in the .NET Framework is an object.

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 (who knows why this isn't a static type)

  • Object

  • Dynamic

You can pass a reference type into a method just like you can pass a static type. It is still considered a parameter. You still use it inside the method like any other variable.

But can reference types be passed, like static types can? Let's try. For instance, if we have a Schedule method for our Calendar class, we could pass in the CourseId or we could pass in the whole Course. It all depends on how we 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, we 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 Integer of the CourseId.

What if we want the second Schedule method to support an optional Course parameter? Say, if I just want it to create a New Course by default if I 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. If I want to do this, I have to accept the CourseId in the Schedule method and construct a new Course in the body of the event.

Output parameters

As Book 1 discusses, 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 creating 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, we should be able to make one of these parameters optional by presetting a value. But, sigh, it doesn't work, as shown in Figure 2-1.

Visual Studio error on default optional parameter value.

Figure 2-1. Visual Studio error on default optional parameter value.

Unlike reference parameters, 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.

Keep in mind the purpose of optional parameters — resolving the need for heavily overloaded methods. Because Output parameters aren't expecting a value coming in any way, it doesn't benefit the programmer to have default values.

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 are supplying!

For example, look at the additall 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? In the old days, you would do this:

int answer = additall(3,7, , ,4);

Fortunately, we don't have to do that anymore. Now, with named parameters, we can say:

int answer = additall(z:3, y:7, v:4);

The nonoptional parameters don't have to be named, because the position is assumed since they are required anyway. Nonetheless, it is good practice to name them. If you skip naming them, you have this instead:

int answer = additall(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.

Overload Resolution

Problems begin when you have optional arguments and overloaded methods in the same method signature. Because C# allows for differently named parameters in overloads, things can get sort of hairy. Take for example:

class Course
{
        public void New(object course)
        {
        }
        public void New(int courseId)
       {
       }
}

Try calling the New method with something like this:

Course course = new Course();
course.New(10);

Here, the runtime picks the second overload because 10 better matches an int than an object. The same is true when dealing with overloaded method signatures with optional parameters. The tiebreaker goes to the overload with the fewest casts required to make it work.

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

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