In Chapter 3, we drew a distinction between value types and reference types. The primitive C# types (int
, char
, etc.) are value types, and when they are created as standalone variables (not as part of other objects) they 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
doesn't 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
VB 6 programmers take note: although there is a performance penalty in using the VB 6 keywords Dim
and New
on the same line, in C#, this penalty doesn't exist. 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 compiler 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 doesn't define a constructor. Because 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 null, etc.).[4] Table 4-2 lists the default values assigned to primitive types.
Table 4-2. Primitive types and their default values
Type | Default value |
---|---|
| 0 |
bool | false |
char | '\0' (null) |
enum | 0 |
Reference types | 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 pass, 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
using System; namespace DeclaringConstructor { 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/2007 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) can't be uninitialized; if you don't tell the constructor what to do, it will set the value types to their default values.
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 the 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.
Tip
Static members are explained a bit later in this chapter.
Examine the highlighted line in Main( )
, where the DateTime
object is created by calling the static property Now
. Now
creates a DateTime
value which, in this case, gets copied to the currentTime
variable on the stack.
The currentTime
variable is passed as a parameter to the Time
constructor. The Time
constructor parameter, dt
, is a copy of the DateTime
object.
It is possible to initialize the values of member variables in an initializer, instead of having to do so in the constructor. You create an initializer by assigning an initial value to a class member:
private int Second = 30; // initializer
Assume that the semantics of the Time
object are such that no matter what time is set, the seconds are always initialized to 30
. You might rewrite the 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 that there are two versions of the constructor that differ by the number and type of parameters. We explain overloading constructors in detail later in this chapter.
Example 4-4. Using an initializer
using System;
namespace 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 );
}
// constructorspublic 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( 2007, 11, 18, 11, 45 );
t2.DisplayCurrentTime( );
}
}
}
Output:
Debug : 11/27/2007 7:52:54
Time : 11/27/2007 7:52:54
Debug : 11/27/2007 7:52:54
Time : 11/18/2007 11:45:30
If you don't 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(2007,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 we invoke DisplayCurrentTime( )
, we call the constructor that takes a DateTime
object, and the seconds are initialized to 54
. The second time the method is invoked, we explicitly set the time to 11:45
(not setting the seconds), and the initializer takes over.
If the program didn't have an initializer and did not otherwise assign a value to Second
, the value would be initialized by the CLR to 0.
An alternative to calling the constructor to initialize the private member values of the Time
class is to use a new feature in C# 3.0 called the object initializer, which allows you to initialize objects without invoking a constructor explicitly. In fact, you need not even define a constructor that initializes the object properties. All you need to do is to specify the value of each public property in the object initializer as shown in Example 4-5.
Example 4-5. Object Initializer
using System; namespace ObjectInitializer { public class Time { // private member variables public int Year; public int Month; public int Date; public int Hour; public int Minute; public int Second = 30; // initializer public void DisplayCurrentTime( ) { System.Console.WriteLine("Time\t: {0}/{1}/{2} {3}:{4}:{5}", Month, Date, Year, Hour, Minute, Second); } } public class Tester { static void Main( ) { Time t = new Time { Year = 2009, Month = 7, Date = 10, Hour = 11, Minute = 15 }; t.DisplayCurrentTime( ); } } } Output: Time : 7/10/2009 11:15:30
The following object initialization:
Time t = new Time { Year = 2009, Month = 7, Date = 10, Hour = 11, Minute = 15 };
Is the equivalent of creating the object and then assigning values to its public members:
Time t = new Time( ); t.Year = 200; t.Month = 7; t.Date = 10; t.Hour = 11; t.Minute = 15;
Warning
The member values Year
, Month
, Date
, Hour
, and Minute
would normally be declared private, and made public through properties, as described later in this chapter.
The keyword this
refers to the current instance of an object. The this
reference (sometimes referred to as a this pointer[5]) is a hidden reference passed to every nonstatic method of a class. Each method can refer to the other methods and variables of its own object by way of the this
reference.
The this
reference is used in a number of ways. 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. Whereas 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 for the parameter and the member variable. The counter argument is that using the same name for 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:
class myClass { public void Foo(OtherClass otherObject) { otherObject.Bar(this); } }
Let's unpack this example. Here, we have a method named myClass.Foo
. In the body of this method, you invoke the Bar
method of the OtherClass
instance, passing in a reference to the current instance of myClass
. This allows the Bar
method to fiddle with the public methods and members of the current instance of myClass
.
The third use of this
is with indexers, covered in Chapter 9.
The fourth use of the this
reference is to call one overloaded constructor from another, for example:
class myClass { public myClass(int i) { //... } public myClass( ) : this(42) { //... } }
In this example, the default constructor invokes the overloaded constructor that takes an integer, by using the this
keyword.
The final way that the this
keyword is used is to explicitly invoke methods and members of a class as a form of documentation:
public void MyMethod(int y) { int x = 0; x = 7; // assign to a local variable y = 8; // assign to a parameter this.z = 5; // assign to a member variable this.Draw( ); // invoke member method }
In the cases shown, the use of the this
reference is superfluous, but may make the programmer's intent clearer and does no harm (except, arguably, to clutter the code).
[4] * When you write your own constructor, you'll find that these values have been initialized before the constructor runs. In a sense, there are two steps to building new objects—some CLR-level magic that zeros out all the fields and does whatever else needs to be done to make the thing a valid object, and then the steps in the constructor you create (if any).
[5] * A pointer is a variable that holds the address of an object in memory. C# doesn't use pointers with managed objects. Some C++ programmers have become so used to talking about a this
pointer that they've carried the term over (incorrectly) to C#. We'll refer to the this
reference, and pay a 25-cent fine to charity each time we forget.
Get Programming C# 3.0, 5th 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.