CONSTRUCTORS
One of the biggest advantages of an
object-oriented programming (OOP) language such as C# is that you can define
special methods that are called whenever an instance of the class is created.
These methods are called constructors. C# introduces a new type of constructor
called a static constructor, which you’ll see in the next section, “Static
Members and Instance Members.”
A key benefit of using a constructor is that it guarantees that
the object will go through proper initialization before being used. When a user
instantiates an object, that object’s constructor is called and must return
before the user can perform any other work with that object. This guarantee
helps ensure the integrity of the object and helps make applications written with
object-oriented languages much more reliable.
But how do you name a constructor so that the compiler will
know to call it when the object is instantiated? The C# designers followed the
lead of the C++ language designer, Bjarne Stroustrup, and dictated that
constructors in C# must have the same name as the class itself. Here’s a simple
class with an equally simple constructor:
using System;
class Constructor1App
{
Constructor1App()
{
Console.WriteLine("[Constructor1App.Constructor1App]
" +
"I'm the constructor");
}
public static void Main()
{
Console.WriteLine("\n[Main] Instantiating a
" +
"Constructor1 object...");
Constructor1App app = new Constructor1App();
}
}
Constructors don’t return values. If
you attempt to prefix the constructor with a type, the compiler will issue an
error stating that you can’t define a member with the same name as the
enclosing type.
You should also note the way objects
are instantiated in C#. This is done using the new keyword with the following
syntax:
<class> <object> = new
<class>(constructor arguments)
If you come from a C++ background,
pay special attention to this. In C++, you can instantiate an object in two
ways. You can declare it on the stack, like this:
// C++ code. This creates an instance of CMyClass on the stack.
CMyClass myClass;
Or you can instantiate the object on
the free store (or heap) by using the C++ new keyword:
// C++ code. This creates an instance of CMyClass on the heap.
CMyClass myClass = new CMyClass();
Instantiating objects is different
in C# than it is in C++, and this difference is a cause for confusion for new
C# developers. The confusion stems from the fact that both languages share a
common keyword for creating objects. Although using the new keyword in C++ lets
you dictate where an object gets created, where an object is created in C#
depends upon the type being instantiated. Having said that, the following code is valid
C# code, but if you’re a C++ developer, it might not do what you expect it too:
MyClass myClass;
In C++, this code would create an
instance of MyClass on the stack. As mentioned, you can create objects in C#
only by using the new keyword. Therefore, this line of code in C# merely
declares that myClass is a variable of type MyClass, but it doesn’t instantiate
the object.
As an example of this, if you
compile the following program, the C# compiler will warn you that the variable
has been declared but isn’t used in the application.
using System;
class Constructor2App
{
Constructor2App()
{
Console.WriteLine("[Constructor2App.Constructor2App]
" +
"I'm the constructor");
}
public static void Main()
{
Console.WriteLine("\n[Main] Declaring, but not
" +
"instantiating, a Constructor2 object...");
Constructor2App app;
}
}
Therefore, if you declare an object
type, you need to instantiate it somewhere in your code by using the new
keyword:
Constructor2App app;
app = new Constructor2App();
Why would you declare an object
without instantiating it? Declaring objects before using them—or “new-ing“
them—is done in cases in which you declare one class inside another. This
nesting of classes is called containment, or aggregation. For example, I might
have an Invoice class that has several embedded classes, such as Customer,
TermCode, and SalesTaxCode.
Static
Members and Instance Members
As
with C++, you can define a member of a class as a static member or an instance
member. By default, each member is defined as an instance member, which means
that a copy of that member is made for every instance of the class. When you
declare a member a static member, only one copy of the member exists. A static
member is created when the application containing the class is loaded, and it
exists throughout the life of the application. Therefore, you can access the
member even before the class has been instantiated. But why would you do this?
One
example involves the Main method. The common language runtime needs to have a
common entry point to your application. You must define a static method called
Main in one of your classes so that the runtime doesn’t have to instantiate one
of your objects. You also use static members when you have a method that, from
an object-oriented perspective, belongs to a class in terms of semantics but
doesn’t need an actual object—for example, if you want to keep track of how
many instances of a given object are created during the lifetime of an
application. Because static members live across object instances, the following
code would work:
using System;
class InstCount
{
public InstCount()
{
instanceCount++;
}
static public int instanceCount;
// instanceCount = 0;
}
class AppClass
{
public static void PrintInstanceCount()
{
Console.WriteLine("[PrintInstanceCount] Now there {0}
" +
"{1} instance{2} of the InstCount class",
InstCount.instanceCount == 1 ?
"is" : "are",
InstCount.instanceCount,
InstCount.instanceCount == 1 ?
"" : "s");
}
public static void Main()
{
PrintInstanceCount();
InstCount ic;
for (int i = 0; i < 2; i++)
{
ic = new InstCount();
Console.WriteLine("[Main] Instantiated a
" +
"{0} object...", ic.GetType());
PrintInstanceCount();
}
}
}
If you build and run this application, you’ll see the results shown in
following Figure
One last note on static members that
are value types: a static member must have a valid value. You can specify this
value when you define the member, as follows:
static public int instanceCount1 = 10;
If you don’t initialize the
variable, the common language runtime will do so upon application startup by
using a default value of 0. Therefore, the following two lines are equivalent:
static public int instanceCount2;
static public int instanceCount2 = 0;
Constants
vs. Read-Only Fields
There will certainly be times when
you have fields that you don’t want altered during the execution of the
application. Examples of fields you might want to protect include the names of
data files your application depends on, certain unchanging values for a math
class such as pi, or even server names and IP addresses that your computer
connects to. Obviously, this list could go on ad infinitum. To address these
situations, C# allows for the definition of two closely related member types:
constants and read-only fields, which I’ll cover in this section.
Constants
As you can guess from the name,
constants—represented by the const keyword—are fields that remain constant for
the life of the application. There are only three rules to keep in mind when
defining something as a const:
- A constant is a member whose value is set at compile
time.
- A constant member’s value must be written as a literal.
- To define a field as a constant, simply specify the
const keyword before the member being defined, as follows:
using System;
class MagicNumbers
{
public const double pi = 3.1415;
public const int answerToAllLifesQuestions = 42;
}
class ConstApp
{
public static void Main()
{
Console.WriteLine("CONSTANTS: pi = {0},"
"everything else = {1}",
MagicNumbers.pi,
MagicNumbers.answerToAllLifesQuestions);
}
}
Read-Only
Fields
A field defined as a const is useful
because it clearly documents the programmer’s intention that the field contains
an immutable value. However, that works only if you know the value at compile
time. So what does a programmer do when the need arises for a field with a
value that won’t be known until run time and shouldn’t be changed once it’s
been initialized? This issue—typically not addressed in other languages—was
resolved by the designers of the C# language with what’s called a read-only
field.
When you define a field with the
readonly keyword, you have the ability to set that field’s value in one place:
the constructor. After that point, the field can’t be changed by the class
itself or the class’s clients. Let’s say that your application needs to keep
track of the current workstation’s IP address. You wouldn’t want to address
this problem with a const because that would entail hard-coding the value—and
even that technique wouldn’t work if the workstation obtains its IP address
dynamically. However, a run-time field would do just the trick:
using System;
using System.Net;
class Workstation
{
public Workstation()
{
IPAddress ipAddress =
Dns.Resolve(HostName).AddressList[0];
IPAddressString = ipAddress.ToString();
}
public const string HostName =
"tyagi";
public readonly string IPAddressString;
}
class GetIpAddress
{
public static void Main()
{
Workstation workstation = new Workstation();
Console.WriteLine("The IP address for '{0}' is {1}",
Workstation.HostName, workstation.IPAddressString);
}
}
Running this application will result in the display of the IP
address associated with the supplied host name (tyagi).
Obviously, this program won’t work correctly if you aren’t connected to a
machine with a host name of “tyagi”. To specify the name of the machine to
which you’re connected, simply modify the WorkStation
class’s const field (HostName) to the name of
the machine whose IP address you’re attempting to determine. Supply your PC’s
machine name for your own workstation IP address.