Team LiB
Previous Section Next Section

Constructors, Revisited

We learned in Chapter 4 that when we instantiate a brand-new object with the new operator, we're creating a "bare bones" object with essentially empty fields (each field will be initialized to 0, null, or whatever is appropriate for a given field type, as we discussed earlier). We also learned that if we want to create an object in a more intelligent fashion—that is, to do more elaborate things when the object is first created—we need to declare a constructor. By way of review, a constructor

Here's one simple example of a constructor for the Student class:

public class Student
{
  // Fields.
  private string name;
  // other details omitted
  // A constructor. (Note: no return type!)
  // This constructor passes in a string value representing the name that is
  // to be assigned to the Student object when it is first instantiated.
  public Student(string n) {
    name = n;
  }

  // etc.
}

We'll now provide some new insights regarding constructors.

Constructor Overloading

We can create many different constructors for the same class that take different combinations of arguments—this is known as overloading, a concept that we discussed in Chapter 5. As long as each constructor has a different argument signature, it's considered to be a different constructor:

// One argument, a string.
public Student(string name) {
  // details omitted ...
}

// Two string arguments; this is OK!
public Student(string name, string ssn) {
  // details omitted ...
}

// One int, one string; this is also OK!
public Student(string name, int id) {
  // details omitted ...
}

If we tried to add the following fourth constructor to the Student class, it would be rejected by the compiler, since there is already another constructor with two string arguments—the fact that the parameter names are different is immaterial.

public Student(string firstName, string lastName) {
  // details omitted ...
}

Replacing the Default Parameterless Constructor

We learned in Chapter 4 that if we don't declare any constructors for a class, a default parameterless constructor is provided by the system that will initialize any fields to their zero-equivalent values. There is one very important caveat about default constructors in C#: if we invent any of our own constructors for a class, with any argument signature, then the default parameterless constructor is not automatically provided. This is by design, because it's assumed that if we've gone to the trouble to program any constructors whatsoever, then we must have some special initialization requirements for our class that the C# default constructor couldn't possibly anticipate.

If we want or need a parameterless constructor for a particular class along with other versions of constructors that do take arguments, we must explicitly program a parameterless constructor ourselves. Generally speaking, it's considered good practice to always explicitly provide a parameterless constructor if we're explicitly providing any other constructors at all.

Here is another version of a Student class, this time with multiple constructors provided; note that here we're indeed replacing the "lost" parameterless constructor (please read in-line comments):

// Student.cs

using System;

public class Student
{
  private string name;
  private string ssn;
  private Professor facultyAdvisor;

  // Constructors.

  // This version takes three arguments.
  public Student(string n, string s, Professor p) {
    name = n;
    ssn = s;
    facultyAdvisor = p;
  }
  // This "flavor" takes two arguments.
  public Student(string n, string s) {
    name = n;
    ssn = s;
    // Since we aren't getting a Professor object handed in to us in
    // this version, we set the facultyAdvisor field to null for the
    // time being. (Strictly speaking, we don't need to do this, as it
    // will automatically be initialized to null anyway -- but, this makes it
    // clear to anyone reading the code.)
    facultyAdvisor = null;
  }

  // We must explicitly provide a parameterless constructor (if we want
  // to be able to use such a constructor) if we have created ANY other
  // constructors.
  public Student() {
    // Note here that we've decided to invent some "placeholder"
    // values for the name and ssn fields in the case where
    // specific values are not being passed in.

    name = "???";
    ssn = "???-??-????";
    facultyAdvisor = null;
  }

  public string Name {
    // Accessor details omitted.
  }

  // etc. for other properties

  public string GetFacultyAdvisorName() {
    // Note: since some of our constructors initialize facultyAdvisor with
    // a Professor object, and others do not, we cannot assume that the
    // field has been initialized to a Professor "handle" when the
    // GetFacultyAdvisorName() method is invoked. To avoid the possibility
    // of throwing a NullReferenceException, we check to make sure that the
    // facultyAdvisor field is NOT null before proceeding.
    if (facultyAdvisor != null) {
      return facultyAdvisor.Name;

    }
    else {
      return "TBD";
    }
  }
}

Here is a simplistic version of a Professor class to use in testing:

// Professor.cs

public class Professor {
  private string name;

  public string Name {
    get {
      return name;
    }
    set {
      name = value;
    }
  }
}

and, here is a main program that exercises the various forms of constructor:

public class MyProgram
{
  static void Main() {
    Student[] students = new Student[3];
    Professor p;

    p = new Professor();
    p.SetName("Dr. Oompah");

    // We'll try out the various Student constructor signatures.
    students[0] = new Student("Joe", "123-45-6789", p);
    students[1] = new Student("Bob", "987-65-4321");
    students[2] = new Student();

    Console.WriteLine("Advisor Information\n");
    for (int i = 0; i < students.Length; i++) {
      Console.WriteLine("Name: " + students[i].Name +
        "\tAdvisor: " +
        students[i].GetFacultyAdvisorName());
    }
  }
}

The preceding program produces the following output when run at the command line:


Advisor Information

Name:  Joe     Advisor:  Dr. Oompah
Name:  Bob     Advisor:  TBD
Name:  ???     Advisor:  TBD

There are some additional complexities that you need to be aware of when it comes to constructors of derived classes and inheritance—we'll discuss these later in this chapter, in the section titled "More About Inheritance and C#."

Reusing Constructor Code Within a Class

We talked about the use of the this keyword for object self-referencing earlier in this chapter; another noteworthy use of the this keyword has to do with reusing constructor code.

If we have a class that declares more than one form of constructor, and we wish to reuse the code from one constructor in the body of another constructor, we can use the expression

: this(optional arguments)

in the declaration of a constructor as a shorthand way of running one constructor from within another. This is best illustrated by a short example.

// Student.cs

using System;

public class Student
{
  private string name;
  private string ssn;
  private Transcript transcript;

  // Constructors.

  // This version takes one argument.
  public Student(string n) {
    name = n;
    transcript = new Transcript();
    // do some other complicated things ...
  }
  // This version takes two arguments. We want to reuse the logic
  // from the preceding constructor without having to repeat the
  // same code in both constructors. We can invoke the one-argument
  // constructor from this two-argument version by using the
  // "this" keyword in the manner shown below:
  public Student(string n, string s) : this(n) {
    // Now, we can go on to do other "extra" things that this version
    // of the constructor needs to take care of.
    ssn = s;
  }

  // etc.
}

By using the syntax : this(n) in this fashion, it's as if we've written the code for the second constructor as follows:

public Student(string n, string s) {
  // Duplicate the code from the first constructor ...
  name = n;
  transcript = new Transcript();
  // do some other complicated things ...

  // ... then go on to do other "extra" things that this version
  // of the constructor needs to take care of.
  ssn = s;
}

but without having to duplicate code from the first constructor. Thus, by using the : this() syntax to reuse constructor code from one version to another, if the logic of the first constructor changes down the road, the second constructor will also benefit.

Because each overloaded version of a constructor in a class is guaranteed to have a unique parameter list, the argument signature being passed into the this() expression will unambiguously select the alternative constructor that is to be invoked.

Note that if constructor version 1 invokes constructor version 2 in this manner, the invocation of constructor version 2 is the first operation performed when the constructor version 1 is invoked. That is, by the time the first explicit line of code of constructor 1 is executed, all of the code of constructor 2 may be assumed to have already executed, as illustrated here:


public Student(string n, string s) : this(n) {
    // The single argument constructor has already run to completion
    // by the time we reach this first line of code ...
    // details omitted.
}

Team LiB
Previous Section Next Section