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.
Tip
VB6
programmers
take
note: While there is a performance penalty in
using the VB6 keywords Dim
and
New
on the same line, in C# this penalty has been
removed. Thus in C# there is no drawback to using the
new
keyword when declaring an object variable.
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.
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, 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 pass,
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 { // private member variables int Year; int Month; int Date; int Hour; int Minute; int Second; // 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;
}
} public class Tester { static void Main( ) {System.DateTime currentTime = System.DateTime.Now;
Time t = new Time(currentTime); t.DisplayCurrentTime( ); } } Output: 11/16/2005 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 property, Now
, which is 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.
It is possible to initialize the values of member variables in an initializer, instead of having to do so in every constructor. 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. See Example 4-4.
Tip
Example 4-4 uses an overloaded constructor, which means there are two versions of the constructor that differ by the number and type of variables. Overloading constructors is explained in detail later in this chapter.
Example 4-4. Using an initializer
public class Time { // private member variables private int Year; private int Month; private int Date; private int Hour; private int Minute;private int Second = 30; // initializer
// 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; } } public class Tester { static void Main( ) { System.DateTime currentTime = System.DateTime.Now; Time t = new Time(currentTime); t.DisplayCurrentTime( ); Time t2 = new Time(2005,11,18,11,45); t2.DisplayCurrentTime( ); } } Output: Debug : 11/27/2005 7:52:54 Time : 11/27/2005 7:52:54 Debug : 11/27/2005 7:52:54 Time : 11/18/2005 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(2005,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.
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 that will create a
new Time
object (t3
).
The .NET Framework defines an
ICloneable
interface to support the concept of a
copy constructor. (Interfaces are covered in detail in Chapter 8.) This interface defines a single method:
Clone( )
. Classes that support the idea of a copy
constructor should implement ICloneable and then should implement
either a shallow copy (calling MemberwiseClone
) or
a deep copy (by calling the copy constructor and hand-copying all the
members).
class SomeType: ICloneable { public Object Clone( ) { return MemberwiseClone( ); // shallow copy } }
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 the second (OtherClass
) with the
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#, Third Edition 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.