#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();
        }

#585 – Optional Parameters Must Come Last

If you define a method that includes optional parameters, they must come after any required parameters.  This means that your options for required vs. optional parameters are:

  • No parameters at all
  • 1 or more required parameters (no default values)
  • 1 or more optional parameters (with default values)
  • 1 or more required parameters, followed by 1 or more optional parameters

        static void NoParams() { }
        static void RequiredOnly(int x, int y) { }
        static void OptionalOnly(int x = 5, int y = 10) { }
        static void RequiredAndOptional(int x, int y, int a = 1, int b = 2) { }

        static void Main()
        {
            NoParams();
            RequiredOnly(5, 10);

            OptionalOnly();
            OptionalOnly(1);
            OptionalOnly(1, 2);

            RequiredAndOptional(1, 1);
            RequiredAndOptional(1, 2, 3);
            RequiredAndOptional(1, 2, 3, 4);
        }

#584 – Defining an Optional Parameter

When you define a method in C#, you can define one or more of the method’s parameters as optional.  An optional parameter is one that has a default value, which allows a calling function to choose whether or not it wants to pass in a value for that parameter.  Any parameters that are omitted by the caller will take on the specified default value.

Here’s the Bark method of a Dog object, which supplies a default value for the numTimesToBark parameter, making it optional.

        public void Bark(string barkSound, int numTimesToBark = 1)
        {
            for (int i = 0; i < numTimesToBark; i++)
                Console.WriteLine(barkSound);
        }

When calling Dog.Bark, the caller can pass in a value for numTimesToBark, or leave off this argument so that the parameter uses the default value (1).

// Pass in both barkSound and numTimesToBark
myDog.Bark("Bow-wow", 4);

// Pass in only barkSound
myDog.Bark("Woof");

#583 – You Can’t Modify the Iterator Variable Within a foreach Loop

The iterator variable within a foreach loop is the variable that takes on the value of each item within the corresponding collection, each time through the loop.

            string[] puppets =
                {"Crow T. Robot", "Howdy Doody", "Kermit",
                 "King Friday XIII", "Lamb Chop"};

            // nextPuppet is the iterator variable
            foreach (string nextPuppet in puppets)
            {
                Console.WriteLine("Puppet: {0}", nextPuppet);
            }

If you try to modify this iterator variable within the loop, however, you’ll get a compile-time error.

foreach (string nextPuppet in puppets)
{
    nextPuppet = "(" + nextPuppet + ")";
    Console.WriteLine("Puppet: {0}", nextPuppet);
}

#582 – Use the as Operator to Unbox to a Nullable Type

You can box regular value types or their equivalent nullable types (e.g. int and int?) and the boxed values will either be null or be of the underlying value type.

You can unbox these values to a nullable type, or use the as operator to do the unboxing.  The example below shows the result of unboxing several different values to a nullable int (int?) using the as operator.

int? i1 = null;   // Nullable<int> w/no value
int? i2 = 42;     // Nullable<int> with a value
int i3 = 12;      // Plain old int

// Boxing nullable types
object o1 = i1;
object o2 = i2;
object o3 = i3;
object o4 = new Dog("I'm not an int", 12);

// Unboxing to nullable types
int? ia1 = o1 as int?;    // null
int? ia2 = o2 as int?;    // 42
int? ia3 = o3 as int?;    // 12
int? ia4 = o4 as int?;    // null

bool bHasVal = ia1.HasValue;  // false
bHasVal = ia2.HasValue;       // true
bHasVal = ia3.HasValue;       // true
bHasVal = ia4.HasValue;       // false

#580 – as Operator Can Generate Compile-Time Errors

The as operator can perform reference conversions between types in a type hierarchy, returning null if the conversion is not possible.  But there are some cases when the compiler knows that no conversion is possible between the specified types, so it will generate an error at compile time.

Dog d = new Dog("Lassie", 12);

// Can't convert a Dog to a Cow--compile-time error
Cow c = d as Cow;

#579 – Typical Pattern for Using as Operator

When you use the as operator to attempt to convert an expression to a particular type, you typically follow the pattern shown below.

In most cases, you have a variable of a base class that stores some object and you use the as operator to determine if it stores an object of a particular derived type.  In the example below, Terrier is a class that inherits from Dog.

            // Dog variable referring to object of type Terrier
            Dog d = new Terrier("Jack", 17, "Crabby");

            // Elsewhere in our code, we have variable
            // of type Dog and want to see if it refers to a
            // Terrier.
            Terrier t = d as Terrier;
            if (t != null)
                t.TerrierMethod();

You can do the same thing with the is operator, though it is a little less efficient, because it actually does the type conversion twice.

            // A bit less efficient
            if (d is Terrier)
                ((Terrier)d).TerrierMethod();

#578 – Using the as Operator to Do Type Conversions

The as operator attempts to convert an expression to a specified type, returning the value of null if the conversion fails.  It behaves similarly to doing the conversion using a cast, but does not throw an exception if the cast fails.

// (Terrier is a sub-class of Dog)

Dog d = new Dog("Fido", 5);
Terrier t = new Terrier("Jack", 17, "Crabby");
Dog d2 = t;

Dog dTest = d as Dog;   // ok
dTest = d as Terrier;   // null

dTest = t as Dog;       // ok
Terrier tTest = t as Terrier;   // ok

dTest = d2 as Dog;      // ok
tTest = d2 as Terrier;  // ok

Object oTest = d as object;    // ok

#577 – Using the is Operator to Check for an Unboxing Conversion

You can use the is operator to check to see whether an object can be unboxed to a particular type.  The operator will return true if the object contains a boxed instance of the specified type.

int i = 12;
double d1 = 4.2;

object o1 = i;   // Boxed int
object o2 = d1;  // Boxed double
object o3 = new object();  // Some object

bool check;

check = o1 is int;       // true - object contains a boxed int
check = o2 is int;       // false - object contains boxed double
check = o3 is int;       // false

#576 – Using the is Operator with Value Typed Objects

You can use the is operator to check the type of value-typed objects.  The result of a check using the is operator is true if the type of the expression matches the specified type exactly.  Because value types don’t support inheritance, an object of one type will never return true for the is operator on a different type, even if the value is assignment compatible with the specified type.

bool check;

byte b = 1;
short s = 2;

check = b is byte;     // true
check = b is short;    // false

// Assignment succeeds
s = b;        // short <= byte

check = s is short;    // true
check = s is byte;     // false

Because all value types inherit from System.ValueType and, indirectly, from System.Object, the is operator always returns true when checking against these types.

check = b is object;   // true
check = s is object;   // true
check = b is System.ValueType;   // true
check = s is System.ValueType;   // true

Follow

Get every new post delivered to your Inbox.

Join 43 other followers