#816 – Named Argument and Optional Parameter Combinations

You use named arguments (vs. positional) to reverse the order of arguments passed in to a method when you call it.

You use optional parameters (vs. required) to to allow omitting an argument for that parameter when you call the method.

Options include:

  • No named arguments, no optional parameters – you must pass in a value for each parameter, in the proper order
  • Named arguments only – you must pass in an argument for each parameter, but you can change the order of the arguments.  Named arguments must follow positional arguments.
  • Optional parameters only – you must pass arguments in the correct order, but may omit one or more arguments if the corresponding parameter is optional.  If you omit an argument for one parameter, you must omit arguments for all parameters that follow that parameter.
  • Named arguments and optional parameters – you can supply any combination of arguments, in any order
Advertisement

#693 – Named Arguments in Constructors Allow the Most Flexibility

If you declare multiple constructors for a class, you can create constructors that support any combination of parameters.

For example:

        public Dog(string name, int age, string favToy)
        {
            // init stuff
        }

        public Dog(string name) : this(name, 1, null) { }
        public Dog(string name, string favToy) : this(name, 1, favToy) { }

Using optional parameters, you can declare a single constructor:

        public Dog(string name, int age = 1, string favToy = null)
        {
            // init stuff
        }

But when you do pass in arguments for any optional parameters, you must pass them in the correct order, without skipping any.

To get around this problem, you can use named arguments when invoking the constructor.

            // Ok to omit age, but then favToy must be named argument
            Dog spot = new Dog("Spot", favToy: "Ball");

#692 – Two Approaches for Optional Parameters in Constructors

When you create an instance of an object, you may want to pass different combinations of parameters for purposes of initializing the object.  In authoring a class, there are a couple different ways to support this.

You can declare multiple constructors, each supporting a different set of parameters.  You can use constructor chaining to invoke the main constructor, to centralize object initialization.

        public Dog(string name, int age, string favToy )
        {
            // Do all parameter validation here
            Name = name;
            Age = age;
            FavoriteToy = favToy;
        }

        public Dog(string name) : this(name, 1, null)
        {
        }

        public Dog(string name, int age) : this(name, age, null)
        {
        }

        public Dog(string name, string favToy) : this(name, 1, favToy)
        {
        }

You can also use optional parameters in a single constructor, to do the same thing.

        public Dog(string name, int age = 1, string favToy = null)
        {
            // Do all parameter validation here
            Name = name;
            Age = age;
            FavoriteToy = favToy;
        }

#594 – When You’d Want to Use Named Arguments

You can change the  order of arguments passed to a method in C# by using named arguments.  Simply changing the order of required arguments probably doesn’t make much sense.  Instead, you’ll likely make use of named arguments as a way of omitting optional parameters.

Without named arguments, when you want to include a value for an optional parameter, you normally have to include values for all arguments up to and including the one that you want to specify a value for.  But with named arguments, you can pick and choose which parameters you provide a value for.

Here’s a Dog.Bark method that has several optional parameters.

public void Bark(string barkSound, int numTimesToBark = 1, int volume = 1, string postBarkSound = null)

When calling Bark, we can choose which arguments to pass.

kirby.Bark("Woof", volume: 5);
kirby.Bark("Grrr", postBarkSound: "Arf");
kirby.Bark("Woof", 3, postBarkSound: "Growf");

#592 – Optional Parameters in Indexers

In addition to defining optional parameters within methods, you can also include an optional parameter in an indexer.

For example, assume that we have a Dog class that keeps track of the sounds that a dog makes when he barks and that we’ve defined an indexer for the class that retrieves a “bark sound” from that list.

        public string this[int i]
        {
            get
            {
                return BarkRecord[i];
            }
        }

Using the indexer:

            kirby.Bark("Woof");
            kirby.Bark("Bow-wow");
            kirby.Bark("Urgh");

            Console.WriteLine(kirby[2]);


We can now add an optional parameter to the indexer, to indicate that we’d like the date/time of the bark returned as well.

        public string this[int i, bool includeTime = false]
        {
            get
            {
                string barkInfo = BarkRecord[i];

                if (includeTime)
                    barkInfo = barkInfo + string.Format(" at {0}", BarkTime[i]);

                return barkInfo;
            }
        }

Using the indexer:

            Console.WriteLine("Bark at [2]={0}", kirby[2]);
            Console.WriteLine("Bark at [1]={0}", kirby[1, true]);

#591 – How Optional Parameters Look with Intellisense

When you’re working in Visual Studio and you start typing in the name of a method, Intellisense will show you any optional parameters that the method has.

In the example below, we’ve started entering code to create a new instance of a Dog object.  Intellisense shows us a list of the optional parameters that are defined for the constructor.

#589 – Optional Parameters Must Be Input Parameters

An optional parameter must be an input parameter.  It can’t therefore be modified by the out or ref keywords.  An optional parameter also cannot be modified by the params keyword.

        // OK: numTimesToRead is an input parameter (passed by value)
        static void ReadBook(string title, int numTimesToRead = 1)
        {
        }

        // NO: out parameter can't have default value
        static void ReadBook2(string title, out int yourRating = 3)
        {
            yourRating = 4;
        }

        // NO: ref parameter can't have default value
        static void ReadBook3(string title, ref string yourOpinion = "Unknown")
        {
        }

        // NO: Parameter array can't have default value
        static void ReadBook4(string title, params string[] characters = null)
        {
        }

#588 – A Default Parameter Value Can Be Null

When defining optional parameters and providing a default value for the parameter, you can use a value of null for a reference-typed parameter.  null is actually the only valid default value that you can use for a reference-typed parameter.

        static void LogDogInfo(Dog myDog, Dog anotherDog = null)
        {
            Console.WriteLine("My dog is {0}", myDog.Name);
            if (anotherDog != null)
                Console.WriteLine("  And there is also {0}", anotherDog.Name);
        }

        static void Main()
        {
            Dog dog1 = new Dog("Kirby", 15);
            Dog dog2 = new Dog("Jack", 17);

            LogDogInfo(dog1);
            LogDogInfo(dog2, dog1);
        }

#587 – If Provided, Optional Arguments Must Be in Correct Order

When you choose to include arguments for optional parameters on a method, you must specify the arguments in the proper order (just like required parameters).

In the example below, we define a method with one required parameter and three optional parameters.  When we call it, we must provide a value for the yourName parameter.  Then we can provide values for one, two or all three of the optional parameters, in the following combinations:

  • book
  • book, play
  • book, play, poem
        static void Favorites(
            string yourName,
            string book = "Moby Dick",
            string play = "Henry V",
            string poem = "The Road Not Taken")
        {
            Console.WriteLine("{0}'s favorites:", yourName);
            Console.WriteLine("  Book: {0}", book);
            Console.WriteLine("  Play: {0}", play);
            Console.WriteLine("  Poem: {0}", poem);
        }

        static void Main()
        {
            Favorites("Sean");
            Favorites("Sergei", "Anna Karenina");
            Favorites("Pablo", "Don Quixote", "Canción de cuna");
            Favorites("Nigel", "David Copperfield", "Hamlet", "Ode to Duty");
        }

#586 – Default Values for Optional Parameters Must Be Constants

When you specify a default value for an optional parameter, that value must be a constant, of the same type as the parameter.  The expression must be able to be evaluted at compile-time.  The parameter’s default value can be one of the following:

  • A constant expression (see below)
  • new S(),  where S is a value-type (parameters not allowed on constructor)

A constant expression is one that can be fully determined at compile-time.  In other words, you can use any expression that you’d use in the initialization of a constant.

For reference types, the expression must be null, with the exception of the string type.

        private const int OFFSET = 1;

        static void Optional1(int x = 5 + OFFSET, double y = 1.0/3.0) { }
        static void Optional2(Point3D fln = new Point3D()) { }
        static void Optional3(Dog d = null) { }
        static void Optional4(string s = "DEFAULT") { }

        static void Main()
        {
            Optional1();
            Optional2();
            Optional3();
        }