Creating Objects

In Chapter 3, a distinction is drawn between value types and reference types. The primitive C# types (int, char, etc.) are value types, and are created on the stack. Objects, however, are reference types, and are created on the heap, using the keyword new, as in the following:

Time t = new Time(  );

t does not actually contain the value for the Time object; it contains the address of that (unnamed) object that is created on the heap. t itself is just a reference to that object.

Constructors

In Example 4-1, notice that the statement that creates the Time object looks as though it is invoking a method:

Time t = new Time(  );

In fact, a method is invoked whenever you instantiate an object. This method is called a constructor, and you must either define one as part of your class definition or let the Common Language Runtime (CLR) provide one on your behalf. The job of a constructor is to create the object specified by a class and to put it into a valid state. Before the constructor runs, the object is undifferentiated memory; after the constructor completes, the memory holds a valid instance of the class type.

The Time class of Example 4-1 does not define a constructor. If a constructor is not declared, the compiler provides one for you. The default constructor creates the object but takes no other action. Member variables are initialized to innocuous values (integers to 0, strings to the empty string, etc.). Table 4-2 lists the default values assigned to primitive types.

Table 4-2. Primitive types and their default values

Type

Default Value

numeric (int, long , etc.)

0

bool

false

char

`\0' (null)

enum

0

reference

null

Typically, you’ll want to define your own constructor and provide it with arguments so that the constructor can set the initial state for your object. In Example 4-1, assume that you want to pass in the current year, month, date, and so forth, so that the object is created with meaningful data.

To define a constructor you declare a method whose name is the same as the class in which it is declared. Constructors have no return type and are typically declared public. If there are arguments to be passed, you define an argument list just as you would for any other method. Example 4-3 declares a constructor for the Time class that accepts a single argument, an object of type DateTime.

Example 4-3. Declaring a constructor

   public class Time
   {
      // public accessor methods
      public void DisplayCurrentTime(  )
      {
         System.Console.WriteLine("{0}/{1}/{2} {3}:{4}:{5}", 
            Month, Date, Year, Hour, Minute, Second);
      }

      // constructor
      public Time(System.DateTime dt)
      {
         
         Year = dt.Year;
         Month = dt.Month;
         Date = dt.Day;
         Hour = dt.Hour;
         Minute = dt.Minute;
         Second = dt.Second;
      }

       // private member variables
       int Year;
       int Month;
       int Date;
       int Hour;
       int Minute;
       int Second;

   }

   public class Tester
   {
      static void Main(  )
      {
         System.DateTime currentTime = System.DateTime.Now;
         Time t = new Time(currentTime);
         t.DisplayCurrentTime(  );
      }

   }

Output:
11/16/2000 16:21:40

In this example, the constructor takes a DateTime object and initializes all the member variables based on values in that object. When the constructor finishes, the Time object exists and the values have been initialized. When DisplayCurrentTime() is called in Main( ) , the values are displayed.

Try commenting out one of the assignments and running the program again. You’ll find that the member variable is initialized by the compiler to 0. Integer member variables are set to 0 if you don’t otherwise assign them. Remember, value types (e.g., integers) cannot be uninitialized; if you don’t tell the constructor what to do, it will try for something innocuous.

In Example 4-3, the DateTime object is created in the Main( ) method of Tester. This object, supplied by the System library, offers a number of public values—Year, Month, Day, Hour, Minute, and Second—that correspond directly to the private member variables of our Time object. In addition, the DateTime object offers a static member method, Now, which returns a reference to an instance of a DateTime object initialized with the current time.

Examine the highlighted line in Main( ), where the DateTime object is created by calling the static method Now( ). Now( )creates a DateTime object on the heap and returns a reference to it.

That reference is assigned to currentTime, which is declared to be a reference to a DateTime object. Then currentTime is passed as a parameter to the Time constructor. The Time constructor parameter, dt, is also a reference to a DateTime object; in fact dt now refers to the same DateTime object as currentTime does. Thus, the Time constructor has access to the public member variables of the DateTime object that was created in Tester.Main( ).

The reason that the DateTime object referred to in the Time constructor is the same object referred to in Main( ) is that objects are reference types. Thus, when you pass one as a parameter it is passed by reference—that is, the pointer is passed and no copy of the object is made.

Initializers

It is possible to initialize the values of member variables in an initializer, instead of having to do so in every constructor. You create an initializer by assigning an initial value to a class member:

                  
private int Second  = 30;  // initializer

Assume that the semantics of our Time object are such that no matter what time is set, the seconds are always initialized to 30. We might rewrite our Time class to use an initializer so that no matter which constructor is called, the value of Second is always initialized, either explicitly by the constructor or implicitly by the initializer, as shown in Example 4-4.

Example 4-4. Using an initializer

     public class Time
   {
      // public accessor methods
      public void DisplayCurrentTime(  )
      {
         System.DateTime now = System.DateTime.Now;
            System.Console.WriteLine(
            "\nDebug\t: {0}/{1}/{2} {3}:{4}:{5}", 
            now.Month, now.Day , now.Year, now.Hour, 
                now.Minute, now.Second);

         System.Console.WriteLine("Time\t: {0}/{1}/{2} {3}:{4}:{5}", 
            Month, Date, Year, Hour, Minute, Second);
      }


      // constructors
      public Time(System.DateTime dt)
      {
         
         Year =      dt.Year;
         Month =     dt.Month;
         Date =      dt.Day;
         Hour =      dt.Hour;
         Minute =    dt.Minute;
         Second =    dt.Second;   //explicit assignment
      }

        public Time(int Year, int Month, int Date, 
            int Hour, int Minute)
        {
            this.Year =     Year;
            this.Month =    Month;
            this.Date =     Date;
            this.Hour =     Hour;
            this.Minute =   Minute;
        }

       // private member variables
      private int Year;
      private int Month;
      private int Date;
      private int Hour;
      private int Minute;
      private int Second  = 30;  // initializer
   }

   public class Tester
   {
      static void Main(  )
      {
         System.DateTime currentTime = System.DateTime.Now;
         Time t = new Time(currentTime);
         t.DisplayCurrentTime(  );

         Time t2 = new Time(2000,11,18,11,45);
         t2.DisplayCurrentTime(  );

      }
   }

Output:
Debug   : 11/27/2000 7:52:54
Time    : 11/27/2000 7:52:54

Debug   : 11/27/2000 7:52:54
Time    : 11/18/2000 11:45:30

If you do not provide a specific initializer, the constructor will initialize each integer member variable to zero (0). In the case shown, however, the Second member is initialized to 30:

private int Second  = 30;  // initializer

If a value is not passed in for Second, its value will be set to 30 when t2 is created:

Time t2 = new Time(2000,11,18,11,45);
t2.DisplayCurrentTime(  );

However, if a value is assigned to Second, as is done in the constructor (which takes a DateTime object, shown in bold), that value overrides the initialized value.

The first time through the program we call the constructor that takes a DateTime object, and the seconds are initialized to 54. The second time through we explicitly set the time to 11:45 (not setting the seconds) and the initializer takes over.

If the program did not have an initializer and did not otherwise assign a value to Second, the value would be initialized by the compiler to zero.

Copy Constructors

A copy constructor creates a new object by copying variables from an existing object of the same type. For example, you might want to pass a Time object to a Time constructor so that the new Time object has the same values as the old one.

C# does not provide a copy constructor, so if you want one you must provide it yourself. Such a constructor copies the elements from the original object into the new one:

public Time(Time existingTimeObject)
{
    Year = existingTimeObject.Year;
    Month = existingTimeObject.Month;
    Date = existingTimeObject.Date;
    Hour = existingTimeObject.Hour;
    Minute = existingTimeObject.Minute;
    Second = existingTimeObject.Second;
}

A copy constructor is invoked by instantiating an object of type Time and passing it the name of the Time object to be copied:

Time t3 = new Time(t2);

Here an existingTimeObject (t2) is passed as a parameter to the copy constructor which will create a new Time object (t3).

The this Keyword

The keyword this refers to the current instance of an object. The this reference (sometimes referred to as a this pointer [7]) is a hidden pointer to every nonstatic method of a class. Each method can refer to the other methods and variables of that object by way of the this reference.

There are three ways in which the this reference is typically used. The first way is to qualify instance members otherwise hidden by parameters, as in the following:

public void SomeMethod (int hour)
{
   this.hour = hour;
}

In this example, SomeMethod( ) takes a parameter (Hour) with the same name as a member variable of the class. The this reference is used to resolve the name ambiguity. While this.Hour refers to the member variable, Hour refers to the parameter.

The argument in favor of this style is that you pick the right variable name and then use it both for the parameter and for the member variable. The counter-argument is that using the same name for both the parameter and the member variable can be confusing.

The second use of the this reference is to pass the current object as a parameter to another method. For instance, the following code

public void FirstMethod(OtherClass otherObject)
{
   otherObject.SecondMethod(this);
}

establishes two classes, one with the method FirstMethod( ), and OtherClass, with its method SecondMethod( ). Inside FirstMethod, we’d like to invoke SecondMethod, passing in the current object for further processing.

The third use of this is with indexers, covered in Chapter 9.



[7] A pointer is a variable that holds the address of an object in memory. C# does not use pointers with managed objects.

Get Programming C# now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.