13.9. Constructors, Revisited

As we get deeper into some of the features of C# we've been passing over until now, we'll need to take another look at constructors. 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 discussed earlier). You also learned that if you want to create an object in a more intelligent fashion—that is, to do more elaborate things when the object is first created—you need to declare a constructor.

By way of review, a constructor

  • Has the same name as the class

  • Has no explicit return type because it really has a default return type matching the class that it's defined for—a constructor returns a brand-new object/instance of that type

  • Can take any number or variety of arguments

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 receives a string value representing the
					// name that is to be assigned to the Student object when it is
					// first instantiated.
					public Student(string name) {
					this.name = name;
					}

  // etc.
}

In the previous example, the this keyword appeared in the constructor because there was a naming conflict in that the parameter in the constructor argument list had the same name (name) as a field declared in the class. The this.name syntax tells the compiler that we are referring to the field instead of the parameter.

We'll now provide some new insights regarding constructors.

13.9.1. 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 id) {
  // 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 because 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...
}

13.9.2. 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, 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.

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 id;
  private Professor facultyAdvisor;

// Constructors.

  // This constructor takes three arguments.
  public Student(string n, string id, Professor p) {
    name = n;
    this.id = id;
    facultyAdvisor = p;
  }

  // This constructor takes two arguments.
  public Student(string n, string id) {
    name = n;
    this.id = id;

    // 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, this isn't
    // necessary, as it will automatically be initialized to null,
    // but this makes it clear to anyone reading the code.)
    facultyAdvisor = null;
  }

  // We provide a parameterless constructor.
  public Student() {
    // Note here that we've decided to invent some "placeholder"
    // values for the name and id fields in the case where
    // specific values are not being passed in.

    name = "???";
    id = "???-??-????";
    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 with a
						// Professor reference when this method is invoked. 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.Name = "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
");
    foreach( Student s in students ) {
      Console.WriteLine("Name: {0}	Advisor: {1} ",
           s.Name, s.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#."

13.9.3. Reusing Constructor Code Within a Class

We talked about the use of the this keyword for object self-referencing earlier in this chapter; another 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 want to reuse the code from one constructor in the body of another constructor, we can use the this keyword in the declaration of a constructor as a shorthand way of running one constructor from within another:

: this(optional arguments)

This is best illustrated by a short example:

// Student.cs

using System;

public class Student
{
  private string name;
  private string id;
  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 id) : this(n) {

// Now, we can go on to do other "extra" things that this
    // version of the constructor needs to take care of.
    this.id = id;
  }

  // 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—but without having to duplicate code from the first constructor:

public Student(string n, string id) {
  // 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.
  this.id = id;
}

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 the code of constructor 2 might be assumed to have already executed, as illustrated here:

public Student(string n, string id) : this(n) {
    // The single argument constructor has already run
						// by the time we reach this next line of code...
    this.id = id;
}

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

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