A C# class (or structure) may define any number of static members via the static keyword. When
you do so, the member in question must be invoked directly from the class level, rather than from a
type instance. To illustrate the distinction, consider our good friend System.Console. As you have
seen, you do not invoke the WriteLine() method from the object level:
// Error! WriteLine() is not an instance level method!
Console c = new Console();
c.WriteLine("I can't be printed...");
but instead simply prefix the type name to the static WriteLine() member:
// Correct! WriteLine() is a static method.
Console.WriteLine("Thanks...");
Simply put, static members are items that are deemed (by the type designer) to be so commonplace
that there is no need to create an instance of the type when invoking the member. While any
class (or structure) can define static members, they are most commonly found within “utility
classes.” For example, if you were to use the Visual Studio 2008 object browser (via the View ä
Object Browser menu item) and examine the members of System.Console, System.Math,
System.Environment, or System.GC (to name a few), you will find that all of their functionality is
exposed from static members.
Defining Static Methods (and Fields)
Assume you have a new Console Application project named StaticMethods and have inserted a
class named Teenager that defines a static method named Complain(). This method returns a random
string, obtained in part by calling a static helper function named GetRandomNumber():
class Teenager
{
public static Random r = new Random();
public static int GetRandomNumber(short upperLimit)
{
return r.Next(upperLimit);
}
public static string Complain()
{
string[] messages = {"Do I have to?", "He started it!",
"I'm too tired...", "I hate school!",
"You are sooooooo wrong!"};
return messages[GetRandomNumber(5)];
}
}
Notice that the System.Random member variable and the GetRandomNumber() helper function
method have also been declared as static members of the Teenager class, given the rule that static
members can operate only on other static members.
nNote Allow me to repeat myself: static members can operate only on static data and call static methods of the
defining class. If you attempt to make use of nonstatic class data or call a nonstatic method of the class within a
static member’s implementation, you’ll receive compile-time errors.
Like any static member, to call Complain(), prefix the name of the defining class:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Methods *****\n");
for(int i =0; i < 5; i++)
Console.WriteLine(Teenager.Complain());
Console.ReadLine();
}
nSource Code The StaticMethods application is located under the Chapter 5 subdirectory.
Defining Static Data
In addition to static members, a type may also define static field data (such as the Random member
variable seen in the previous Teenager class). Understand that when a class defines nonstatic data
(properly referred to as instance data), each object of this type maintains an independent copy of
the field. For example, assume a class that models a savings account is defined in a new Console
Application project named StaticData:
// A simple savings account class.
class SavingsAccount
{
public double currBalance;
public SavingsAccount(double balance)
{
currBalance = balance;
}
}
When you create SavingsAccount objects, memory for the currBalance field is allocated for
each class instance. Static data, on the other hand, is allocated once and shared among all objects
of the same type. To illustrate the usefulness of static data, add a static point of data named
currInterestRate to the SavingsAccount class, which is set to a default value of 0.04:
// A simple savings account class.
class SavingsAccount
{
public double currBalance;
// A static point of data.
public static double currInterestRate = 0.04;
public SavingsAccount(double balance)
{
currBalance = balance;
}
}
If you were to create three instances of SavingsAccount as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
Dim s1 As new SavingsAccount(50);
Dim s2 As new SavingsAccount(100);
Dim s3 As new SavingsAccount(10000.75);
Console.ReadLine();
}
the in-memory data allocation would look something like Figure 5-5.
Figure 5-5. Static data is allocated once and shared among all instances of the class.
Let’s update the SavingsAccount class to define two static methods to get and set the interest
rate value:
// A simple savings account class.
class SavingsAccount
{
public double currBalance;
// A static point of data.
public static double currInterestRate = 0.04;
public SavingsAccount(double balance)
{
currBalance = balance;
}
// Static members to get/set interest rate.
public static void SetInterestRate(double newRate )
{ currInterestRate = newRate; }
public static double GetInterestRate()
{ return currInterestRate; }
}
Now, observe the following usage and the output in Figure 5-6:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
SavingsAccount s1 = new SavingsAccount(50);
SavingsAccount s2 = new SavingsAccount(100);
// Print the current interest rate.
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
Savings Account:S1
// Make new object, this does NOT 'reset' the interest rate.
SavingsAccount s3 = new SavingsAccount(10000.75);
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
Console.ReadLine();
}
Figure 5-6. Static data is allocated only once.
As you can see, when you create new instances of the SavingsAccount class, the value of the
static data is not reset, as the CLR will allocate the data into memory exactly one time. After that
point, all objects of type SavingsAccount operate on the same value.
As stated, static methods can operate only on static data. However, a nonstatic method can
make use of both static and nonstatic data. This should make sense, given that static data is
available to all instances of the type. To illustrate, update SavingsAccount with the following
instance-level members:
class SavingsAccount
{
public double currBalance;
public static double currInterestRate = 0.04;
// Instance members to get/set interest rate.
public void SetInterestRateObj(double newRate)
{ currInterestRate = newRate; }
public double GetInterestRateObj()
{ return currInterestRate; }
...
}
Here, SetInterestRateObj() and GetInterestRateObj() are operating on the same static field
as the static SetInterestRate()/GetInterestRate() methods. Thus, if one object were to change the
interest rate, all other objects report the same value:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
SavingsAccount.SetInterestRate(0.09);
SavingsAccount s1 = new SavingsAccount(50);
SavingsAccount s2 = new SavingsAccount(100);
// All three lines print out "Interest Rate is: 0.09"
Console.WriteLine("Interest Rate is: {0}", s1.GetInterestRateObj());
Console.WriteLine("Interest Rate is: {0}", s2.GetInterestRateObj());
Console.WriteLine("Interest Rate is: {0}", SavingsAccount.GetInterestRate());
Console.ReadLine();
}
In this case, the value 0.09 is returned regardless of which SavingsAccount object we ask
(including asking via the static GetInterestRate() method).
Defining Static Constructors
As explained earlier in this chapter, constructors are used to set the value of a type’s data at the time
of creation. Thus, if you were to assign a value to a static data member within an instance-level constructor,
you may be surprised to find that the value is reset each time you create a new object! For
example, assume you have updated the SavingsAccount class as follows:
class SavingsAccount
{
public double currBalance;
public static double currInterestRate;
public SavingsAccount(double balance)
{
currInterestRate = 0.04;
currBalance = balance;
}
...
}
If you execute the previous Main() method, notice how the currInterestRate variable is reset
each time you create a new SavingsAccount object (see Figure 5-7).
Figure 5-7. Assigning static data in an instance level constructor “resets” the value.
While you are always free to establish the initial value of static data using the member initialization
syntax, what if the value for your static data needed to be obtained from a database or
external file? To perform such tasks requires a method scope (such as a constructor) to execute the
code statements. For this very reason, C# allows you to define a static constructor:
class SavingsAccount
{
public double currBalance;
public static double currInterestRate;
public SavingsAccount(double balance)
{
currBalance = balance;
}
// A static constructor.
static SavingsAccount()
{
Console.WriteLine("In static ctor!");
CHAPTER 5 n DEFINING ENCAPSULATED CLASS TYPES 157
currInterestRate = 0.04;
}
...
}
Simply put, a static constructor is a special constructor that is an ideal place to initialize the
values of static data when the value is not known at compile time (e.g., you need to read in the value
from an external file, generate a random number, etc.). Here are a few points of interest regarding
static constructors:
• A given class (or structure) may define only a single static constructor.
• A static constructor does not take an access modifier and cannot take any parameters.
• A static constructor executes exactly one time, regardless of how many objects of the type are
created.
• The runtime invokes the static constructor when it creates an instance of the class or before
accessing the first static member invoked by the caller.
• The static constructor executes before any instance-level constructors.
Given this modification, when you create new SavingsAccount objects, the value of the static
data is preserved, as the static member is set only one time within the static constructor, regardless
of the number of objects created.
Defining Static Classes
Since the release of .NET 2.0, the C# language expanded the scope of the static keyword by introducing
static classes. When a class has been defined as static, it is not creatable using the new
keyword, and it can contain only members or fields marked with the static keyword (if this is not
the case, you receive compiler errors).
At first glance, this might seem like a fairly useless feature, given that a class that cannot be created
does not appear all that helpful. However, if you create a class that contains nothing but static
members and/or constant data, the class has no need to be allocated in the first place. Consider the
following new static class type:
// Static classes can only
// contain static members!
static class TimeUtilClass
{
public static void PrintTime()
{ Console.WriteLine(DateTime.Now.ToShortTimeString()); }
public static void PrintDate()
{ Console.WriteLine(DateTime.Today.ToShortDateString()); }
}
Given that this class has been defined with the static keyword, we cannot create an instance
of TimeUtilClass using the new keyword:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Static Data *****\n");
TimeUtilClass.PrintDate();
// Compiler error! Can't create static classes.
TimeUtilClass u = new TimeUtilClass ();
...
}
Prior to .NET 2.0, the only way to prevent the creation of a class type was to either redefine the
default constructor as private or mark the class as an abstract type using the C# abstract keyword
(full details regarding abstract types are in Chapter 6):
class TimeUtilClass
{
// Redefine the default ctor as private
// to prevent creation.
private TimeUtilClass (){}
public static void PrintTime()
{ Console.WriteLine(DateTime.Now.ToShortTimeString()); }
public static void PrintDate()
{ Console.WriteLine(DateTime.Today.ToShortDateString()); }
}
// Define type as abstract to prevent
// creation
abstract class TimeUtilClass
{
public static void PrintTime()
{ Console.WriteLine(DateTime.Now.ToShortTimeString()); }
public static void PrintDate()
{ Console.WriteLine(DateTime.Today.ToShortDateString()); }
}
While these constructs are still permissible, the use of static classes is a cleaner solution and
more type-safe, given that the previous two techniques allowed nonstatic members to appear
within the class definition without error.
On a related note, a project’s application object (e.g., the class defining the Main() method) is
often defined as a static class, to ensure it only contains static members and cannot be directly
created. For example:
// Define the application object as static.
static class Program
{
static void Main(string[] args)
{
...
}
}
At this point in the chapter you hopefully feel comfortable defining simple class types containing
constructors, fields, and various static (and nonstatic) members. Now that you have the basics
under your belt, we can formally investigate the three pillars of object-oriented programming.
nSource Code The StaticData project is located under the Chapter 5 subdirectory.
Defining the Pillars of OOP
All object-based languages must contend with three core principals of object-oriented programming,
often called the “pillars of object-oriented programming (OOP)”:
• Encapsulation: How does this language hide an object’s internal implementation details and
preserve data integrity?
• Inheritance: How does this language promote code reuse?
• Polymorphism: How does this language let you treat related objects in a similar way?
Before digging into the syntactic details of each pillar, it is important that you understand the
basic role of each. Here is an overview of each pillar, which will be examined in full detail over the
remainder of this chapter and the next.
The Role of Encapsulation
The first pillar of OOP is called encapsulation. This trait boils down to the language’s ability to hide
unnecessary implementation details from the object user. For example, assume you are using a
class named DatabaseReader, which has two primary methods: Open() and Close():
// This type encapsulates the details of opening and closing a database.
DatabaseReader dbReader = new DatabaseReader();
dbReader.Open(@"C:\MyCars.mdf");
// Do something with data file and close the file.
dbReader.Close();
The fictitious DatabaseReader class encapsulates the inner details of locating, loading, manipulating,
and closing the data file. Object users love encapsulation, as this pillar of OOP keeps
programming tasks simpler. There is no need to worry about the numerous lines of code that are
working behind the scenes to carry out the work of the DatabaseReader class. All you do is create an
instance and send the appropriate messages (e.g., “Open the file named MyCars.mdf located on my
C drive”).
Closely related to the notion of encapsulating programming logic is the idea of data hiding.
Ideally, an object’s state data should be specified using the private (or possibly protected) keyword.
In this way, the outside world must ask politely in order to change or obtain the underlying value.
This is a good thing, as publicly declared data points can easily become corrupted (hopefully by
accident rather than intent!). You will formally examine this aspect of encapsulation in just a bit.
The Role of Inheritance
The next pillar of OOP, inheritance, boils down to the language’s ability to allow you to build new
class definitions based on existing class definitions. In essence, inheritance allows you to extend the
behavior of a base (or parent) class by inheriting core functionality into the derived subclass (also
called a child class). Figure 5-8 shows a simple example.
You can read the diagram in Figure 5-8 as “A Hexagon is-a Shape that is-an Object.” When you
have classes related by this form of inheritance, you establish “is-a” relationships between types.
The “is-a” relationship is termed classical inheritance.
Here, you can assume that Shape defines some number of members that are common to all
descendents. Given that the Hexagon class extends Shape, it inherits the core functionality defined by
Shape and Object, as well as defines additional hexagon-related details of its own (whatever those
may be).
nNote Under the .NET platform, System.Object is always the topmost parent in any class hierarchy, which
There is another form of code reuse in the world of OOP: the containment/delegation model
(also known as the “has-a” relationship or aggregation). This form of reuse is not used to establish
parent/child relationships. Rather, the “has-a” relationship allows one class to define a member
variable of another class and expose its functionality (if required) to the object user indirectly.
For example, assume you are again modeling an automobile. You might want to express the
idea that a car “has-a” radio. It would be illogical to attempt to derive the Car class from a Radio, or
vice versa (a Car “is-a” Radio? I think not!). Rather, you have two independent classes working
together, where the Car class creates and exposes the Radio’s functionality:
class Radio
{
public void Power(bool turnOn)
{
Console.WriteLine("Radio on: {0}", turnOn);
}
}
class Car
{
// Car 'has-a' Radio
private Radio myRadio = new Radio();
public void TurnOnRadio(bool onOff)
{
// Delegate call to inner object.
myRadio.Power(onOff);
}
}
Notice that the object user has no clue that the Car class is making use of an inner Radio object.
static void Main(string[] args)
{
// Call is forwarded to Radio internally.
Car viper = new Car();
viper.TurnOnRadio(false);
}
The Role of Polymorphism
The final pillar of OOP is polymorphism. This trait captures a language’s ability to treat related
objects in a similar manner. Specifically, this tenant of an object-oriented language allows a base
class to define a set of members (formally termed the polymorphic interface) that are available to all
descendents. A class’s polymorphic interface is constructed using any number of virtual or abstract
members
In a nutshell, a virtual member is a member in a base class that defines a default implementation
that may be changed (or more formally speaking, overridden) by a derived class. In contrast, an
abstract method is a member in a base class that does not provide a default implementation, but
does provide a signature. When a class derives from a base class defining an abstract method, it
must be overridden by a derived type. In either case, when derived types override the members
defined by a base class, they are essentially redefining how they respond to the same request.
To preview polymorphism, let’s provide some details behind the shapes hierarchy shown in
Figure 5-8. Assume that the Shape class has defined a virtual method named Draw() that takes no
parameters. Given the fact that every shape needs to render itself in a unique manner, subclasses
(such as Hexagon and Circle) are free to override this method to their own liking (see Figure 5-9).
Figure 5-9. Classical polymorphism
Once a polymorphic interface has been designed, you can begin to make various assumptions
in your code. For example, given that Hexagon and Circle derive from a common parent (Shape), an
array of Shape types could contain anything deriving from this base class. Furthermore, given that
Shape defines a polymorphic interface to all derived types (the Draw() method in this example), we
can assume each member in the array has this functionality.
Consider the following Main() method, which instructs an array of Shape-derived types to render
themselves using the Draw() method:
class Program
{
static void Main(string[] args)
{
Shape[] myShapes = new Shape[3];
myShapes[0] = new Hexagon();
myShapes[1] = new Circle();
myShapes[2] = new Hexagon();
foreach (Shape s in myShapes)
{
s.Draw();
}
Console.ReadLine();
}
}
This wraps up our brisk overview of the pillars of OOP. Now that you have the theory in your
mind, the remainder of this chapter explores further details of how encapsulation is handled under
C#. The next chapter will tackle the details of inheritance and polymorphism.
C# Access Modifiers
When working with encapsulation, you must always take into account which aspects of a type are
visible to various parts of your application. Specifically, types (classes, interfaces, structures, enumerations,
delegates) and their members (properties, methods, constructors, fields, and so forth)
are always defined using a specific keyword to control how “visible” the item is to other parts of
your application. Although C# defines numerous keywords to control access, they differ on where
they can be successfully applied (type or member). Table 5-1 documents the role of each access
modifier and where it may be applied.
Table 5-1. C# Access Modifiers
C# Access Modifier May Be Applied To Meaning in Life
public Types or type members Public items have no access
restrictions. A public member can be
accessed from an object as well as
any derived class. A public type can
be accessed from other external
assemblies.
private Type members or nested types Private items can only be accessed
by the class (or structure) that
defines the item.
protected Type members or nested types Protected items are not directly
accessible from an object variable;
however, they are accessible by the
defining type as well as by derived
classes.
internal Types or type members Internal items are accessible only
within the current assembly.
Therefore, if you define a set of
internal types within a .NET class
library, other assemblies are not able
to make use of them.
protected internal Type members or nested types When the protected and internal
keywords are combined on an item,
the item is accessible within the
defining assembly, the defining
class, and by derived classes.
In this chapter, we are only concerned with the public and private keywords. Later chapters
will examine the role of the internal and protected internal modifiers (useful when you build
.NET code libraries) and the protected modifier (useful when you are creating class hierarchies).
The Default Access Modifiers
By default, type members are implicitly private while types are implicitly internal. Thus, the
following class definition is automatically set to internal, while the type’s default constructor is
automatically set to private:
// An internal class with a private default constructor.
class Radio
{
Radio(){}
}
Thus, to allow other types to invoke members of an object, you must mark them as publically
accessible. As well, if you wish to expose the Radio to external assemblies (again, useful when building
.NET code libraries; see Chapter 15) you will need to add the public modifier.
// A public class with a public default constructor.
public class Radio
{
public Radio(){}
}
Access Modifiers and Nested Types
As mentioned in Table 5-1, the private, protected, and protected internal access modifiers can be
applied to a nested type. Chapter 6 will examine nesting in detail. What you need to know at this
point, however, is that a nested type is a type declared directly within the scope of class or structure.
By way of example, here is a private enumeration (named Color) nested within a public class
(named SportsCar):
public class SportsCar
{
// OK! Nested types can be marked private.
private enum CarColor
{
Red, Green, Blue
}
}
Here, it is permissible to apply the private access modifier on the nested type. However,
nonnested types (such as the SportsCar) can only be defined with the public or internal modifiers.
Therefore, the following class definition is illegal:
// Error! Nonnested types cannot be marked private!
private class SportsCar
{}
The First Pillar: C#’s Encapsulation Services
The concept of encapsulation revolves around the notion that an object’s internal data should not
be directly accessible from an object instance. Rather, if the caller wants to alter the state of an
object, the user does so indirectly using accessor (i.e., “getter”) and mutator (i.e., “setter”) methods.
In C#, encapsulation is enforced at the syntactic level using the public, private, internal, and
protected keywords. To illustrate the need for encapsulation services, assume you have created the
following class definition:
// A class with a single public field.
class Book
{
public int numberOfPages;
}
The problem with public field data is that the items have no ability to intrinsically “understand”
whether the current value to which they are assigned is valid with regard to the current
business rules of the system. As you know, the upper range of a C# int is quite large (2,147,483,647).
Therefore, the compiler allows the following assignment:
// Humm. That is one heck of a mini-novel!
static void Main(string[] args)
{
Book miniNovel = new Book();
miniNovel.numberOfPages = 30000000;
}
Although you have not overflowed the boundaries of an int data type, it should be clear that a
mini-novel with a page count of 30,000,000 pages is a bit unreasonable. As you can see, public fields
do not provide a way to trap logical upper (or lower) limits. If your current system has a business
rule that states a book must be between 1 and 1,000 pages, you are at a loss to enforce this programmatically.
Because of this, public fields typically have no place in a production-level class definition.
Encapsulation provides a way to preserve the integrity of an object’s state data. Rather than
defining public fields (which can easily foster data corruption), you should get in the habit of defining
private data, which is indirectly manipulated using one of two main techniques:
• Define a pair of accessor (get) and mutator (set) methods.
• Define a type property.
Additionally, C# provides the readonly keyword, which also delivers a level of data protection.
Whichever technique you choose, the point is that a well-encapsulated class should hide the details
of how it operates from the prying eyes of the outside world. This is often termed black box programming.
The beauty of this approach is that an object is free to change how a given method is
implemented under the hood. It does this without breaking any existing code making use of it, provided
that the signature of the method remains constant.
Encapsulation Using Traditional Accessors and Mutators
Over the remaining pages in this chapter, we will be building a fairly complete class that models a
general employee. To get the ball rolling, create a new Console Application named EmployeeApp
and insert a new class file (named Employee.cs) using the Project äAdd class menu item. Update
the Employee class with the following fields, methods, and constructors:
class Employee
{
// Field data.
private string empName;
private int empID;
private float currPay;
// Constructors.
public Employee() {}
public Employee(string name, int id, float pay)
{
empName = name;
empID = id;
currPay = pay;
}
// Members.
public void GiveBonus(float amount)
{
currPay += amount;
}
public void DisplayStats()
{
Console.WriteLine("Name: {0}", empName);
Console.WriteLine("ID: {0}", empID);
Console.WriteLine("Pay: {0}", currPay);
}
}
Notice that the fields of the Employee class are currently defined using the private access keyword.
Given this, the empName, empID, and currPay fields are not directly accessible from an object
variable:
static void Main(string[] args)
{
// Error! Cannot directly access private members
// from an object!
Employee emp = new Employee();
emp.empName = "Marv";
}
If you want the outside world to interact with your private string representing a worker’s full
name, tradition dictates defining an accessor (get method) and a mutator (set method). For example,
to encapsulate the empName field, you could add the following public members to the existing
Employee class type:
class Employee
{
// Field data.
private string empName;
...
// Accessor (get method)
public string GetName()
{
return empName;
}
// Mutator (set method)
public void SetName(string name)
{
// Remove any illegal characters (!,@,#,$,%),
// check maximum length or case before making assignment.
empName = name;
}
}
This technique requires two uniquely named methods to operate on a single data point. To
illustrate, update your Main() method as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Encapsulation *****\n");
Employee emp = new Employee("Marvin", 456, 30000);
emp.GiveBonus(1000);
emp.DisplayStats();
// Use the get/set methods to interact with the object's name.
emp.SetName("Marv");
Console.WriteLine("Employee is named: {0}", emp.GetName());
Console.ReadLine();
}
Encapsulation Using Type Properties
Although you can encapsulate a piece of field data using traditional get and set methods, .NET languages
prefer to enforce data protection using properties. First of all, understand that properties
always map to “real” accessor and mutator methods in terms of CIL code. Therefore, as a class
designer, you are still able to perform any internal logic necessary before making the value assignment
(e.g., uppercase the value, scrub the value for illegal characters, check the bounds of a
numerical value, and so on).
Here is the updated Employee class, now enforcing encapsulation of each field using property
syntax rather than traditional get and set methods:
class Employee
{
// Field data.
private string empName;
private int empID;
private float currPay;
// Properties.
public string Name
{
get { return empName; }
set { empName = value; }
}
public int ID
{
get { return empID; }
set { empID = value; }
}
public float Pay
{
get { return currPay; }
set { currPay = value; }
}
...
}
A C# property is composed by defining a get scope (accessor) and set scope (mutator) directly
within the property scope itself. Once we have these properties in place, it appears to the caller that
it is getting and setting a public point of data; however, the correct get and set block is called
behind the scenes to preserve encapsulation:
CHAPTER 5 n DEFINING ENCAPSULATED CLASS TYPES 167
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Encapsulation *****\n");
Employee emp = new Employee("Marvin", 456, 30000);
emp.GiveBonus(1000);
emp.DisplayStats();
// Set and get the Name property.
emp.Name = "Marv";
Console.WriteLine("Employee is named: {0}", emp.Name);
Console.ReadLine();
}
Properties (as opposed to accessors and mutators) also make your types easier to manipulate,
in that properties are able to respond to the intrinsic operators of C#. To illustrate, assume that the
Employee class type has an internal private member variable representing the age of the employee.
Here is the relevant update:
class Employee
{
...
private int empAge;
public int Age
{
get { return empAge; }
set { empAge = value; }
}
// Constructors
public Employee() {}
public Employee(string name, int age, int id, float pay)
{
empName = name;
empID = id;
empAge = age;
currPay = pay;
}
public void DisplayStats()
{
Console.WriteLine("Name: {0}", empName);
Console.WriteLine("ID: {0}", empID);
Console.WriteLine("Age: {0}", empAge);
Console.WriteLine("Pay: {0}", currPay);
}
}
Now assume you have created an Employee object named joe. On his birthday, you wish to
increment the age by one. Using traditional accessor and mutator methods, you would need to
write code such as the following:
Employee joe = new Employee();
joe.SetAge(joe.GetAge() + 1);
However, if you encapsulate empAge using a property named Age, you are able to simply write
Employee joe = new Employee();
joe.Age++;
Thursday, October 22, 2009
Understanding Class Constructors
Given that objects have state (represented by the values of an object’s member variables), the object
user will typically want to assign relevant values to the object’s field data before use. Currently, the
Car type demands that the petName and currSpeed fields be assigned on a field-by-field basis. For the
current example, this is not too problematic, given that we have only two public data points. However,
it is not uncommon for a class to have dozens of fields to contend with. Clearly, it would be
undesirable to author 20 initialization statements to set 20 points of data.
Thankfully, C# supports the use of class constructors, which allow the state of an object to be
established at the time of creation. A constructor is a special method of a class that is called indirectly
when creating an object using the new keyword. However, unlike a “normal” method,
constructors never have a return value (not even void) and are always named identically to the
class they are constructing.
nNote As shown in Chapter 13, C# 2008 provides a new object initialization syntax, which allows you to set the
values of public fields and invoke public properties at the time of construction.
The Role of the Default Constructor
Every C# class is provided with a freebee default constructor that you may redefine if need be. By
definition, a default constructor never takes arguments. Beyond allocating the new object into
memory, the default constructor ensures that all field data is set to an appropriate default value
(see Chapter 3 for information regarding the default values of C# data types).
144 CHAPTER 5 n DEFINING ENCAPSULATED CLASS TYPES
If you are not satisfied with these default assignments, you may redefine the default constructor
to suit your needs. To illustrate, update your C# Car class as follows:
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
// A custom default constructor.
public Car()
{
petName = "Chuck";
currSpeed = 10;
}
...
}
In this case, we are forcing all Car objects to begin life named Chuck at a rate of 10 mph. With
this, you are able to create a Car object set to these default values as follows:
static void Main(string[] args)
{
// Invoking the default constructor.
Car chuck = new Car();
// Prints "Chuck is going 10 MPH."
chuck.PrintState();
}
Defining Custom Constructors
Typically, classes define additional constructors beyond the default. In doing so, you provide the
object user with a simple and consistent way to initialize the state of an object directly at the time
of creation. Ponder the following update to the Car class, which now supports a total of three class
constructors:
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
// A custom default constructor.
public Car()
{
petName = "Chuck";
currSpeed = 10;
}
// Here, currSpeed will receive the
// default value of an int (zero).
public Car(string pn)
{
petName = pn;
}
// Let caller set the full 'state' of the Car.
public Car(string pn, int cs)
{
petName = pn;
currSpeed = cs;
}
...
}
Keep in mind that what makes one constructor different from another (in the eyes of the C#
compiler) is the number of and type of constructor arguments. Recall from Chapter 4, when you
define a method of the same name that differs by the number or type of arguments, you have
overloaded the method. Thus, the Car type has overloaded the constructor to provide a number of
ways to create the object at the time of declaration. In any case, you are now able to create Car
objects using any of the public constructors. For example:
static void Main(string[] args)
{
// Make a Car called Chuck going 10 MPH.
Car chuck = new Car();
chuck.PrintState();
// Make a Car called Mary going 0 MPH.
Car mary = new Car("Mary");
mary.PrintState();
// Make a Car called Daisy going 75 MPH.
Car daisy = new Car("Daisy", 75);
daisy.PrintState();
}
The Default Constructor Revisited
As you have just learned, all classes are endowed with a free default constructor. Thus, if you insert a
new class into your current project named Motorcycle, defined like so:
class Motorcycle
{
public void PopAWheely()
{
Console.WriteLine("Yeeeeeee Haaaaaeewww!");
}
}
you are able to create an instance of the Motorcycle type via the default constructor out of the box:
static void Main(string[] args)
{
Motorcycle mc = new Motorcycle();
mc.PopAWheely();
}
However, as soon as you define a custom constructor, the default constructor is silently removed
from the class and is no longer available! Think of it this way: if you do not define a custom constructor,
the C# compiler grants you a default in order to allow the object user to allocate an
instance of your type with field data set to the correct default values. However, when you define
a unique constructor, the compiler assumes you have taken matters into your own hands.
Therefore, if you wish to allow the object user to create an instance of your type with the
default constructor, as well as your custom constructor, you must explicitly redefine the default. To
this end, understand that in a vast majority of cases, the implementation of the default constructor
of a class is intentionally empty, as all you require is the ability to create an object with default
values. Consider the following update to the Motorcycle class:
class Motorcycle
{
public int driverIntensity;
public void PopAWheely()
{
for (int i = 0; i <= driverIntensity; i++)
{
Console.WriteLine("Yeeeeeee Haaaaaeewww!");
}
}
// Put back the default constructor.
public Motorcycle() {}
// Our custom constructor.
public Motorcycle(int intensity)
{ driverIntensity = intensity; }
}
The Role of the this Keyword
Like other C-based languages, C# supplies a this keyword that provides access to the current class
instance. One possible use of the this keyword is to resolve scope ambiguity, which can arise when
an incoming parameter is named identically to a data field of the type. Of course, ideally you would
simply adopt a naming convention that does not result in such ambiguity; however, to illustrate this
use of the this keyword, update your Motorcycle class with a new string field (named name) to represent
the driver’s name. Next, add a method named SetDriverName() implemented as follows:
class Motorcycle
{
public int driverIntensity;
public string name;
public void SetDriverName(string name)
{ name = name; }
...
}
Although this code will compile just fine, if you update Main() to call SetDriverName() and then
print out the value of the name field, you may be surprised to find that the value of the name field is an
empty string!
// Make a Motorcycle with a rider named Tiny?
Motorcycle c = new Motorcycle(5);
c.SetDriverName("Tiny");
c.PopAWheely();
Console.WriteLine("Rider name is {0}", c.name); // Prints an empty name value!
The problem is that the implementation of SetDriverName() is assigning the incoming parameter
back to itself given that the compiler assumes name is referring to the variable currently in the
method scope rather than the name field at the class scope. To inform the compiler that you wish to
set the current object’s name data field to the incoming name parameter, simply use this to resolve
the ambiguity:
public void SetDriverName(string name)
{ this.name = name; }
Do understand that if there is no ambiguity, you are not required to make use of the this keyword
when a class wishes to access its own data or members. For example, if we rename the string
data member to driverName, the use of this is optional as there is no longer a scope ambiguity:
class Motorcycle
{
public int driverIntensity;
public string driverName;
public void SetDriverName(string name)
{
// These two statements are functionally the same.
driverName = name;
this.driverName = name;
}
...
}
Even though there is little to be gained when using this in unambiguous situations, you may
still find this keyword useful when implementing members, as IDEs such as SharpDevelop and
Visual Studio 2008 will enable IntelliSense when this is specified. This can be very helpful when
you have forgotten the name of a class member and want to quickly recall the definition. Consider
Note It is a compiler error to use the this keyword within the implementation of a static member (explained
shortly). As you will see, static members operate on the class (not object) level, and therefore at the class level,
there is no current object (thus no this)!
Chaining Constructor Calls Using this
Another use of the this keyword is to design a class using a technique termed constructor chaining.
This design pattern is helpful when you have a class that defines multiple constructors. Given the
fact that constructors often validate the incoming arguments to enforce various business rules, it
can be quite common to find redundant validation logic within a class’s constructor set. Consider
the following updated Motorcycle:
class Motorcycle
{
public int driverIntensity;
public string driverName;
public Motorcycle() { }
// Redundent constructor logic!
public Motorcycle(int intensity)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
}
public Motorcycle(int intensity, string name)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
...
}
Here (perhaps in an attempt to ensure the safety of the rider), each constructor is ensuring that
the intensity level is never greater than 10. While this is all well and good, we do have redundant
code statements in two constructors. This is less than ideal, as we are now required to update code
in multiple locations if our rules change (for example, if the intensity should not be greater than 5).
One way to improve the current situation is to define a method in the Motorcycle class that will
validate the incoming argument(s). If we were to do so, each constructor could make a call to this
method before making the field assignment(s). While this approach does allow us to isolate the
code we need to update when the business rules change, we are now dealing with the following
redundancy:
class Motorcycle
{
public int driverIntensity;
public string driverName;
// Constructors.
public Motorcycle() { }
public Motorcycle(int intensity)
{
SetIntensity(intensity);
}
public Motorcycle(int intensity, string name)
{
SetIntensity(intensity);
driverName = name;
}
public void SetIntensity(int intensity)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
}
...
}
A cleaner approach is to designate the constructor that takes the greatest number of arguments
as the “master constructor” and have its implementation perform the required validation logic. The
remaining constructors can make use of the this keyword to forward the incoming arguments to
the master constructor and provide any additional parameters as necessary. In this way, we only
need to worry about maintaining a single constructor for the entire class, while the remaining constructors
are basically empty.
Here is the final iteration of the Motorcycle class (with one additional constructor for the sake
of illustration). When chaining constructors, note how the this keyword is “dangling” off the constructor’s
declaration (via a colon operator) outside the scope of the constructor itself:
class Motorcycle
{
public int driverIntensity;
public string driverName;
// Constructor chaining.
public Motorcycle() {}
public Motorcycle(int intensity)
: this(intensity, "") {}
public Motorcycle(string name)
: this(0, name) {}
// This is the 'master' constructor that does all the real work.
public Motorcycle(int intensity, string name)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
...
}
Understand that using the this keyword to chain constructor calls is never mandatory. However,
when you make use of this technique, you do tend to end up with a more maintainable and
concise class definition. Again, using this technique you can simplify your programming tasks, as
the real work is delegated to a single constructor (typically the constructor that has the most parameters),
while the other constructors simply “pass the buck.”
Observing Constructor Flow
On a final note, do know that once a constructor passes arguments to the designated master constructor
(and that constructor has processed the data), the constructor invoked originally by the
caller will finish executing any remaining code statements. To clarify, update each of the constructors
of the Motorcycle class with a fitting call to Console.WriteLine():
class Motorcycle
{
public int driverIntensity;
public string driverName;
// Constructor chaining.
public Motorcycle()
{
Console.WriteLine("In default ctor");
}
public Motorcycle(int intensity)
: this(intensity, "")
{
Console.WriteLine("In ctor taking an int");
}
public Motorcycle(string name)
: this(0, name)
{
Console.WriteLine("In ctor taking a string");
}
// This is the 'master' constructor that does all the real work.
public Motorcycle(int intensity, string name)
{
Console.WriteLine("In master ctor ");
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
...
}
Now, ensure your Main() method exercises a Motorcycle object as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with class Types *****\n");
// Make a Motorcycle.
Motorcycle c = new Motorcycle(5);
c.SetDriverName("Tiny");
c.PopAWheely();
Console.WriteLine("Rider name is {0}", c.driverName);
Console.ReadLine();
}
As you can see, the flow of constructor logic is as follows:
• We create our object by invoking the constructor requiring a single int.
• This constructor forwards the supplied data to the master constructor and provides any
additional startup arguments not specified by the caller.
• The master constructor assigns the incoming data to the object’s field data.
• Control is returned to the constructor originally called, and executes any remaining code
statements.
Great! At this point you are able to define a class with field data (aka member variables) and
various members that can be created using any number of constructors. Next up, let’s formalize
user will typically want to assign relevant values to the object’s field data before use. Currently, the
Car type demands that the petName and currSpeed fields be assigned on a field-by-field basis. For the
current example, this is not too problematic, given that we have only two public data points. However,
it is not uncommon for a class to have dozens of fields to contend with. Clearly, it would be
undesirable to author 20 initialization statements to set 20 points of data.
Thankfully, C# supports the use of class constructors, which allow the state of an object to be
established at the time of creation. A constructor is a special method of a class that is called indirectly
when creating an object using the new keyword. However, unlike a “normal” method,
constructors never have a return value (not even void) and are always named identically to the
class they are constructing.
nNote As shown in Chapter 13, C# 2008 provides a new object initialization syntax, which allows you to set the
values of public fields and invoke public properties at the time of construction.
The Role of the Default Constructor
Every C# class is provided with a freebee default constructor that you may redefine if need be. By
definition, a default constructor never takes arguments. Beyond allocating the new object into
memory, the default constructor ensures that all field data is set to an appropriate default value
(see Chapter 3 for information regarding the default values of C# data types).
144 CHAPTER 5 n DEFINING ENCAPSULATED CLASS TYPES
If you are not satisfied with these default assignments, you may redefine the default constructor
to suit your needs. To illustrate, update your C# Car class as follows:
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
// A custom default constructor.
public Car()
{
petName = "Chuck";
currSpeed = 10;
}
...
}
In this case, we are forcing all Car objects to begin life named Chuck at a rate of 10 mph. With
this, you are able to create a Car object set to these default values as follows:
static void Main(string[] args)
{
// Invoking the default constructor.
Car chuck = new Car();
// Prints "Chuck is going 10 MPH."
chuck.PrintState();
}
Defining Custom Constructors
Typically, classes define additional constructors beyond the default. In doing so, you provide the
object user with a simple and consistent way to initialize the state of an object directly at the time
of creation. Ponder the following update to the Car class, which now supports a total of three class
constructors:
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
// A custom default constructor.
public Car()
{
petName = "Chuck";
currSpeed = 10;
}
// Here, currSpeed will receive the
// default value of an int (zero).
public Car(string pn)
{
petName = pn;
}
// Let caller set the full 'state' of the Car.
public Car(string pn, int cs)
{
petName = pn;
currSpeed = cs;
}
...
}
Keep in mind that what makes one constructor different from another (in the eyes of the C#
compiler) is the number of and type of constructor arguments. Recall from Chapter 4, when you
define a method of the same name that differs by the number or type of arguments, you have
overloaded the method. Thus, the Car type has overloaded the constructor to provide a number of
ways to create the object at the time of declaration. In any case, you are now able to create Car
objects using any of the public constructors. For example:
static void Main(string[] args)
{
// Make a Car called Chuck going 10 MPH.
Car chuck = new Car();
chuck.PrintState();
// Make a Car called Mary going 0 MPH.
Car mary = new Car("Mary");
mary.PrintState();
// Make a Car called Daisy going 75 MPH.
Car daisy = new Car("Daisy", 75);
daisy.PrintState();
}
The Default Constructor Revisited
As you have just learned, all classes are endowed with a free default constructor. Thus, if you insert a
new class into your current project named Motorcycle, defined like so:
class Motorcycle
{
public void PopAWheely()
{
Console.WriteLine("Yeeeeeee Haaaaaeewww!");
}
}
you are able to create an instance of the Motorcycle type via the default constructor out of the box:
static void Main(string[] args)
{
Motorcycle mc = new Motorcycle();
mc.PopAWheely();
}
However, as soon as you define a custom constructor, the default constructor is silently removed
from the class and is no longer available! Think of it this way: if you do not define a custom constructor,
the C# compiler grants you a default in order to allow the object user to allocate an
instance of your type with field data set to the correct default values. However, when you define
a unique constructor, the compiler assumes you have taken matters into your own hands.
Therefore, if you wish to allow the object user to create an instance of your type with the
default constructor, as well as your custom constructor, you must explicitly redefine the default. To
this end, understand that in a vast majority of cases, the implementation of the default constructor
of a class is intentionally empty, as all you require is the ability to create an object with default
values. Consider the following update to the Motorcycle class:
class Motorcycle
{
public int driverIntensity;
public void PopAWheely()
{
for (int i = 0; i <= driverIntensity; i++)
{
Console.WriteLine("Yeeeeeee Haaaaaeewww!");
}
}
// Put back the default constructor.
public Motorcycle() {}
// Our custom constructor.
public Motorcycle(int intensity)
{ driverIntensity = intensity; }
}
The Role of the this Keyword
Like other C-based languages, C# supplies a this keyword that provides access to the current class
instance. One possible use of the this keyword is to resolve scope ambiguity, which can arise when
an incoming parameter is named identically to a data field of the type. Of course, ideally you would
simply adopt a naming convention that does not result in such ambiguity; however, to illustrate this
use of the this keyword, update your Motorcycle class with a new string field (named name) to represent
the driver’s name. Next, add a method named SetDriverName() implemented as follows:
class Motorcycle
{
public int driverIntensity;
public string name;
public void SetDriverName(string name)
{ name = name; }
...
}
Although this code will compile just fine, if you update Main() to call SetDriverName() and then
print out the value of the name field, you may be surprised to find that the value of the name field is an
empty string!
// Make a Motorcycle with a rider named Tiny?
Motorcycle c = new Motorcycle(5);
c.SetDriverName("Tiny");
c.PopAWheely();
Console.WriteLine("Rider name is {0}", c.name); // Prints an empty name value!
The problem is that the implementation of SetDriverName() is assigning the incoming parameter
back to itself given that the compiler assumes name is referring to the variable currently in the
method scope rather than the name field at the class scope. To inform the compiler that you wish to
set the current object’s name data field to the incoming name parameter, simply use this to resolve
the ambiguity:
public void SetDriverName(string name)
{ this.name = name; }
Do understand that if there is no ambiguity, you are not required to make use of the this keyword
when a class wishes to access its own data or members. For example, if we rename the string
data member to driverName, the use of this is optional as there is no longer a scope ambiguity:
class Motorcycle
{
public int driverIntensity;
public string driverName;
public void SetDriverName(string name)
{
// These two statements are functionally the same.
driverName = name;
this.driverName = name;
}
...
}
Even though there is little to be gained when using this in unambiguous situations, you may
still find this keyword useful when implementing members, as IDEs such as SharpDevelop and
Visual Studio 2008 will enable IntelliSense when this is specified. This can be very helpful when
you have forgotten the name of a class member and want to quickly recall the definition. Consider
Note It is a compiler error to use the this keyword within the implementation of a static member (explained
shortly). As you will see, static members operate on the class (not object) level, and therefore at the class level,
there is no current object (thus no this)!
Chaining Constructor Calls Using this
Another use of the this keyword is to design a class using a technique termed constructor chaining.
This design pattern is helpful when you have a class that defines multiple constructors. Given the
fact that constructors often validate the incoming arguments to enforce various business rules, it
can be quite common to find redundant validation logic within a class’s constructor set. Consider
the following updated Motorcycle:
class Motorcycle
{
public int driverIntensity;
public string driverName;
public Motorcycle() { }
// Redundent constructor logic!
public Motorcycle(int intensity)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
}
public Motorcycle(int intensity, string name)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
...
}
Here (perhaps in an attempt to ensure the safety of the rider), each constructor is ensuring that
the intensity level is never greater than 10. While this is all well and good, we do have redundant
code statements in two constructors. This is less than ideal, as we are now required to update code
in multiple locations if our rules change (for example, if the intensity should not be greater than 5).
One way to improve the current situation is to define a method in the Motorcycle class that will
validate the incoming argument(s). If we were to do so, each constructor could make a call to this
method before making the field assignment(s). While this approach does allow us to isolate the
code we need to update when the business rules change, we are now dealing with the following
redundancy:
class Motorcycle
{
public int driverIntensity;
public string driverName;
// Constructors.
public Motorcycle() { }
public Motorcycle(int intensity)
{
SetIntensity(intensity);
}
public Motorcycle(int intensity, string name)
{
SetIntensity(intensity);
driverName = name;
}
public void SetIntensity(int intensity)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
}
...
}
A cleaner approach is to designate the constructor that takes the greatest number of arguments
as the “master constructor” and have its implementation perform the required validation logic. The
remaining constructors can make use of the this keyword to forward the incoming arguments to
the master constructor and provide any additional parameters as necessary. In this way, we only
need to worry about maintaining a single constructor for the entire class, while the remaining constructors
are basically empty.
Here is the final iteration of the Motorcycle class (with one additional constructor for the sake
of illustration). When chaining constructors, note how the this keyword is “dangling” off the constructor’s
declaration (via a colon operator) outside the scope of the constructor itself:
class Motorcycle
{
public int driverIntensity;
public string driverName;
// Constructor chaining.
public Motorcycle() {}
public Motorcycle(int intensity)
: this(intensity, "") {}
public Motorcycle(string name)
: this(0, name) {}
// This is the 'master' constructor that does all the real work.
public Motorcycle(int intensity, string name)
{
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
...
}
Understand that using the this keyword to chain constructor calls is never mandatory. However,
when you make use of this technique, you do tend to end up with a more maintainable and
concise class definition. Again, using this technique you can simplify your programming tasks, as
the real work is delegated to a single constructor (typically the constructor that has the most parameters),
while the other constructors simply “pass the buck.”
Observing Constructor Flow
On a final note, do know that once a constructor passes arguments to the designated master constructor
(and that constructor has processed the data), the constructor invoked originally by the
caller will finish executing any remaining code statements. To clarify, update each of the constructors
of the Motorcycle class with a fitting call to Console.WriteLine():
class Motorcycle
{
public int driverIntensity;
public string driverName;
// Constructor chaining.
public Motorcycle()
{
Console.WriteLine("In default ctor");
}
public Motorcycle(int intensity)
: this(intensity, "")
{
Console.WriteLine("In ctor taking an int");
}
public Motorcycle(string name)
: this(0, name)
{
Console.WriteLine("In ctor taking a string");
}
// This is the 'master' constructor that does all the real work.
public Motorcycle(int intensity, string name)
{
Console.WriteLine("In master ctor ");
if (intensity > 10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
...
}
Now, ensure your Main() method exercises a Motorcycle object as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with class Types *****\n");
// Make a Motorcycle.
Motorcycle c = new Motorcycle(5);
c.SetDriverName("Tiny");
c.PopAWheely();
Console.WriteLine("Rider name is {0}", c.driverName);
Console.ReadLine();
}
As you can see, the flow of constructor logic is as follows:
• We create our object by invoking the constructor requiring a single int.
• This constructor forwards the supplied data to the master constructor and provides any
additional startup arguments not specified by the caller.
• The master constructor assigns the incoming data to the object’s field data.
• Control is returned to the constructor originally called, and executes any remaining code
statements.
Great! At this point you are able to define a class with field data (aka member variables) and
various members that can be created using any number of constructors. Next up, let’s formalize
Introducing the C# Class Type
As far as the .NET platformis concerned, the most fundamental programming construct is the class
type. Formally, a class is a user-defined type that is composed of field data (often called member
variables) and members that operate on this data (such as constructors, properties, methods,
events, and so forth). Collectively, the set of field data represents the “state” of a class instance (otherwise
known as an object). The power of object-based languages such as C# is that by grouping
data and related functionality in a class definition, you are able to model your software after entities
in the real world.
To get the ball rolling, create a new C# Console Application named SimpleClassExample. Next,
insert a new class file (named Car.cs) into your project using the Project äAdd Class menu selection,
A class is defined in C# using the class keyword. Here is the simplest possible declaration:
class Car
{
}
Once you have defined a class type, you will need to consider the set of member variables that
will be used to represent its state. For example, you may decide that cars maintain an int data type
to represent the current speed and a string data type to represent the car’s friendly pet name. Given
these initial design notes, update your Car class as follows:
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
}
Notice that these member variables are declared using the public access modifier. Public
members of a class are directly accessible once an object of this type has been created. As you may
already know, the term“object” is used to represent an instance of a given class type created using
the new keyword.
nNote Field data of a class should seldom (if ever) be defined as public. To preserve the integrity of your state
data, it is a far better design to define data as private (or possibly protected) and allow controlled access to the
data via type properties (as shown later in this chapter). However, to keep this first example as simple as possible,
public data fits the bill.
After you have defined the set of member variables that represent the state of the type, the next
design step is to establish the members that model its behavior. For this example, the Car class will
define one method named SpeedUp() and another named PrintState():
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
// The functionality of the Car.
public void PrintState()
{
Console.WriteLine("{0} is going {1} MPH.", petName, currSpeed);
}
public void SpeedUp(int delta)
{
currSpeed += delta;
}
}
As you can see, PrintState() is more or less a diagnostic function that will simply dump the
current state of a given Car object to the command window. SpeedUp() will increase the speed of the
Car by the amount specified by the incoming int parameter. Now, update your Main() method with
the following code:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Class Types *****\n");
// Allocate and configure a Car object.
Car myCar = new Car();
myCar.petName = "Henry";
myCar.currSpeed = 10;
// Speed up the car a few times and print out the
// new state.
for (int i = 0; i <= 10; i++)
{
myCar.SpeedUp(5);
myCar.PrintState();
}
Console.ReadLine();
}
Allocating Objects with the new Keyword
As shown in the previous code example, objects must be allocated into memory using the new keyword.
If you do not make use of the new keyword and attempt to make use of your class variable in a
subsequent code statement, you will receive a compiler error:
static void Main(string[] args)
{
// Error! Forgot to use 'new'!
Car myCar;
myCar.petName = "Fred";
}
To correctly create a class type variable, you may define and allocate a Car object on a single
line of code:
static void Main(string[] args)
{
Car myCar = new Car();
myCar.petName = "Fred";
}
As an alternative, if you wish to define and allocate an object on separate lines of code, you
may do so as follows:
static void Main(string[] args)
{
Car myCar;
myCar = new Car();
myCar.petName = "Fred";
}
Here, the first code statement simply declares a reference to a yet-to-be-determined Car object.
It is not until you assign a reference to an object via the new keyword that this reference points to a
valid class instance in memory.
In any case, at this point we have a trivial class type that defines a few points of data and some
basic methods. To enhance the functionality of the current Car type, we need to understand the role
of class constructors.
type. Formally, a class is a user-defined type that is composed of field data (often called member
variables) and members that operate on this data (such as constructors, properties, methods,
events, and so forth). Collectively, the set of field data represents the “state” of a class instance (otherwise
known as an object). The power of object-based languages such as C# is that by grouping
data and related functionality in a class definition, you are able to model your software after entities
in the real world.
To get the ball rolling, create a new C# Console Application named SimpleClassExample. Next,
insert a new class file (named Car.cs) into your project using the Project äAdd Class menu selection,
A class is defined in C# using the class keyword. Here is the simplest possible declaration:
class Car
{
}
Once you have defined a class type, you will need to consider the set of member variables that
will be used to represent its state. For example, you may decide that cars maintain an int data type
to represent the current speed and a string data type to represent the car’s friendly pet name. Given
these initial design notes, update your Car class as follows:
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
}
Notice that these member variables are declared using the public access modifier. Public
members of a class are directly accessible once an object of this type has been created. As you may
already know, the term“object” is used to represent an instance of a given class type created using
the new keyword.
nNote Field data of a class should seldom (if ever) be defined as public. To preserve the integrity of your state
data, it is a far better design to define data as private (or possibly protected) and allow controlled access to the
data via type properties (as shown later in this chapter). However, to keep this first example as simple as possible,
public data fits the bill.
After you have defined the set of member variables that represent the state of the type, the next
design step is to establish the members that model its behavior. For this example, the Car class will
define one method named SpeedUp() and another named PrintState():
class Car
{
// The 'state' of the Car.
public string petName;
public int currSpeed;
// The functionality of the Car.
public void PrintState()
{
Console.WriteLine("{0} is going {1} MPH.", petName, currSpeed);
}
public void SpeedUp(int delta)
{
currSpeed += delta;
}
}
As you can see, PrintState() is more or less a diagnostic function that will simply dump the
current state of a given Car object to the command window. SpeedUp() will increase the speed of the
Car by the amount specified by the incoming int parameter. Now, update your Main() method with
the following code:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Class Types *****\n");
// Allocate and configure a Car object.
Car myCar = new Car();
myCar.petName = "Henry";
myCar.currSpeed = 10;
// Speed up the car a few times and print out the
// new state.
for (int i = 0; i <= 10; i++)
{
myCar.SpeedUp(5);
myCar.PrintState();
}
Console.ReadLine();
}
Allocating Objects with the new Keyword
As shown in the previous code example, objects must be allocated into memory using the new keyword.
If you do not make use of the new keyword and attempt to make use of your class variable in a
subsequent code statement, you will receive a compiler error:
static void Main(string[] args)
{
// Error! Forgot to use 'new'!
Car myCar;
myCar.petName = "Fred";
}
To correctly create a class type variable, you may define and allocate a Car object on a single
line of code:
static void Main(string[] args)
{
Car myCar = new Car();
myCar.petName = "Fred";
}
As an alternative, if you wish to define and allocate an object on separate lines of code, you
may do so as follows:
static void Main(string[] args)
{
Car myCar;
myCar = new Car();
myCar.petName = "Fred";
}
Here, the first code statement simply declares a reference to a yet-to-be-determined Car object.
It is not until you assign a reference to an object via the new keyword that this reference points to a
valid class instance in memory.
In any case, at this point we have a trivial class type that defines a few points of data and some
basic methods. To enhance the functionality of the current Car type, we need to understand the role
of class constructors.
Understanding C# Nullable Types
To wrap up this chapter, let’s examine the role of nullable data type using a final Console Application
named NullableTypes. As you know, CLR data types have a fixed range and are represented as a
type in the System namespace. For example, the System.Boolean data type can be assigned a value
from the set {true, false}. Now, recall that all of the numerical data types (as well as the Boolean
data type) are value types. As a rule, value types can never be assigned the value of null, as that is
used to establish an empty object reference:
static void Main(string[] args)
{
// Compiler errors!
// Value types cannot be set to null!
bool myBool = null;
int myInt = null;
// OK! Strings are reference types.
string myString = null;
}
Since the release of .NET 2.0, it has been possible to create nullable data types. Simply put, a
nullable type can represent all the values of its underlying type, plus the value null. Thus, if we
declare a nullable System.Boolean, it could be assigned a value from the set {true, false, null}.
This can be extremely helpful when working with relational databases, given that it is quite common
to encounter undefined columns in database tables. Without the concept of a nullable data
type, there is no convenient manner in C# to represent a numerical data point with no value.
To define a nullable variable type, the question mark symbol (?) is suffixed to the underlying
data type. Do note that this syntax is only legal when applied to value types. If you attempt to create
a nullable reference type (including strings), you are issued a compile-time error. Like a nonnullable
variable, local nullable variables must be assigned an initial value:
static void LocalNullableVariables()
{
// Define some local nullable types.
int? nullableInt = 10;
double? nullableDouble = 3.14;
bool? nullableBool = null;
char? nullableChar = 'a';
int?[] arrayOfNullableInts = new int?[10];
// Error! Strings are reference types!
// string? s = "oops";
}
In C#, the ? suffix notation is a shorthand for creating an instance of the generic System.
Nullable structure type. Although we will not examine generics until Chapter 10, it is important
to understand that the System.Nullable type provides a set of members that all nullable types
can make use of.
For example, you are able to programmatically discover whether the nullable variable indeed
has been assigned a null value using the HasValue property or the != operator. The assigned value of
a nullable type may be obtained directly or via the Value property. Given that the ? suffix is just a
shorthand for using Nullable, you could implement your LocalNullableVariables() method as
follows:
static void LocalNullableVariables()
{
// Define some local nullable types using Nullable.
Nullable nullableInt = 10;
Nullable nullableDouble = 3.14;
Nullable nullableBool = null;
Nullable nullableChar = 'a';
Nullable[] arrayOfNullableInts = new int?[10];
}
Working with Nullable Types
As stated, nullable data types can be particularly useful when you are interacting with databases,
given that columns in a data table may be intentionally empty (e.g., undefined). To illustrate,
assume the following class, which simulates the process of accessing a database that has a table
containing two columns that may be null. Note that the GetIntFromDatabase() method is not
assigning a value to the nullable integer member variable, while GetBoolFromDatabase() is assigning
a valid value to the bool? member:
class DatabaseReader
{
// Nullable data field.
public int? numericValue = null;
public bool? boolValue = true;
// Note the nullable return type.
public int? GetIntFromDatabase()
{ return numericValue; }
// Note the nullable return type.
public bool? GetBoolFromDatabase()
{ return boolValue; }
}
Now, assume the following Main() method, which invokes each member of the DatabaseReader
class, and discovers the assigned values using the HasValue and Value members as well as using the
C# equality operator (not-equal, to be exact):
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Nullable Data *****\n");
DatabaseReader dr = new DatabaseReader();
// Get int from "database".
int? i = dr.GetIntFromDatabase();
if (i.HasValue)
Console.WriteLine("Value of 'i' is: {0}", i.Value);
else
Console.WriteLine("Value of 'i' is undefined.");
// Get bool from "database".
bool? b = dr.GetBoolFromDatabase();
if (b != null)
Console.WriteLine("Value of 'b' is: {0}", b.Value);
else
Console.WriteLine("Value of 'b' is undefined.");
Console.ReadLine();
}
The ?? Operator
The final aspect of nullable types to be aware of is that they can make use of the C# ?? operator.
This operator allows you to assign a value to a nullable type if the retrieved value is in fact null. For
this example, assume you wish to assign a local nullable integer to 100 if the value returned from
GetIntFromDatabase() is null (of course, this method is programmed to always return null, but I
am sure you get the general idea):
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Nullable Data *****\n");
DatabaseReader dr = new DatabaseReader();
...
// If the value from GetIntFromDatabase() is null,
// assign local variable to 100.
int? myData = dr.GetIntFromDatabase() ?? 100;
Console.WriteLine("Value of myData: {0}", myData.Value);
Console.ReadLine();
}
named NullableTypes. As you know, CLR data types have a fixed range and are represented as a
type in the System namespace. For example, the System.Boolean data type can be assigned a value
from the set {true, false}. Now, recall that all of the numerical data types (as well as the Boolean
data type) are value types. As a rule, value types can never be assigned the value of null, as that is
used to establish an empty object reference:
static void Main(string[] args)
{
// Compiler errors!
// Value types cannot be set to null!
bool myBool = null;
int myInt = null;
// OK! Strings are reference types.
string myString = null;
}
Since the release of .NET 2.0, it has been possible to create nullable data types. Simply put, a
nullable type can represent all the values of its underlying type, plus the value null. Thus, if we
declare a nullable System.Boolean, it could be assigned a value from the set {true, false, null}.
This can be extremely helpful when working with relational databases, given that it is quite common
to encounter undefined columns in database tables. Without the concept of a nullable data
type, there is no convenient manner in C# to represent a numerical data point with no value.
To define a nullable variable type, the question mark symbol (?) is suffixed to the underlying
data type. Do note that this syntax is only legal when applied to value types. If you attempt to create
a nullable reference type (including strings), you are issued a compile-time error. Like a nonnullable
variable, local nullable variables must be assigned an initial value:
static void LocalNullableVariables()
{
// Define some local nullable types.
int? nullableInt = 10;
double? nullableDouble = 3.14;
bool? nullableBool = null;
char? nullableChar = 'a';
int?[] arrayOfNullableInts = new int?[10];
// Error! Strings are reference types!
// string? s = "oops";
}
In C#, the ? suffix notation is a shorthand for creating an instance of the generic System.
Nullable
to understand that the System.Nullable
can make use of.
For example, you are able to programmatically discover whether the nullable variable indeed
has been assigned a null value using the HasValue property or the != operator. The assigned value of
a nullable type may be obtained directly or via the Value property. Given that the ? suffix is just a
shorthand for using Nullable
follows:
static void LocalNullableVariables()
{
// Define some local nullable types using Nullable
Nullable
Nullable
Nullable
Nullable
Nullable
}
Working with Nullable Types
As stated, nullable data types can be particularly useful when you are interacting with databases,
given that columns in a data table may be intentionally empty (e.g., undefined). To illustrate,
assume the following class, which simulates the process of accessing a database that has a table
containing two columns that may be null. Note that the GetIntFromDatabase() method is not
assigning a value to the nullable integer member variable, while GetBoolFromDatabase() is assigning
a valid value to the bool? member:
class DatabaseReader
{
// Nullable data field.
public int? numericValue = null;
public bool? boolValue = true;
// Note the nullable return type.
public int? GetIntFromDatabase()
{ return numericValue; }
// Note the nullable return type.
public bool? GetBoolFromDatabase()
{ return boolValue; }
}
Now, assume the following Main() method, which invokes each member of the DatabaseReader
class, and discovers the assigned values using the HasValue and Value members as well as using the
C# equality operator (not-equal, to be exact):
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Nullable Data *****\n");
DatabaseReader dr = new DatabaseReader();
// Get int from "database".
int? i = dr.GetIntFromDatabase();
if (i.HasValue)
Console.WriteLine("Value of 'i' is: {0}", i.Value);
else
Console.WriteLine("Value of 'i' is undefined.");
// Get bool from "database".
bool? b = dr.GetBoolFromDatabase();
if (b != null)
Console.WriteLine("Value of 'b' is: {0}", b.Value);
else
Console.WriteLine("Value of 'b' is undefined.");
Console.ReadLine();
}
The ?? Operator
The final aspect of nullable types to be aware of is that they can make use of the C# ?? operator.
This operator allows you to assign a value to a nullable type if the retrieved value is in fact null. For
this example, assume you wish to assign a local nullable integer to 100 if the value returned from
GetIntFromDatabase() is null (of course, this method is programmed to always return null, but I
am sure you get the general idea):
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Nullable Data *****\n");
DatabaseReader dr = new DatabaseReader();
...
// If the value from GetIntFromDatabase() is null,
// assign local variable to 100.
int? myData = dr.GetIntFromDatabase() ?? 100;
Console.WriteLine("Value of myData: {0}", myData.Value);
Console.ReadLine();
}
Understanding Value Types and Reference Types
Note The following discussion of value types and reference types assumes that you have a background in
object-oriented programming.We will examine a number of topics that assume you have a background in objectoriented
programming. If this is not the case, you may wish to reread this section once you have completed
Unlike arrays, strings, or enumerations, C# structures do not have an identically named representation
in the .NET library (that is, there is no System.Structure class), but are implicitly derived from
System.ValueType. Simply put, the role of System.ValueType is to ensure that the derived type (e.g.,
any structure) is allocated on the stack rather than the garbage collected heap.
Functionally, the only purpose of System.ValueType is to “override” the virtual methods defined
by System.Object to use value-based, versus reference-based, semantics. As you may know, overriding
is the process of changing the implementation of a virtual (or possibly abstract) method defined
within a base class. The base class of ValueType is System.Object. In fact, the instance methods
defined by System.ValueType are identical to those of System.Object:
// Structures and enumerations extend System.ValueType.
public abstract class ValueType : object
{
public virtual bool Equals(object obj);
public virtual int GetHashCode();
public Type GetType();
public virtual string ToString();
}
Given the fact that value types are using value-based semantics, the lifetime of a structure
(which includes all numerical data types [int, float, etc.], as well as any enum or custom structure)
is very predictable. When a structure variable falls out of the defining scope, it is removed from
memory immediately:
// Local structures are popped off
// the stack when a method returns.
static void LocalValueTypes()
{
// Recall! "int" is really a System.Int32 structure.
int i = 0;
// Recall! Point is a structure type.
Point p = new Point();
} // "i" and "p" popped off the stack here!
Value Types, References Types, and the Assignment Operator
When you assign one value type to another, a member-by-member copy of the field data is
achieved. In the case of a simple data type such as System.Int32, the only member to copy is the
numerical value. However, in the case of our Point, the X and Y values are copied into the new
structure variable. To illustrate, create a new Console Application project named
ValueAndReferenceTypes and copy your previous Point definition into your new namespace.
Now, add the following method to your Program type:
// Assigning two intrinsic value types results in
// two independent variables on the stack.
static void ValueTypeAssignment()
{
Console.WriteLine("Assigning value types\n");
Point p1 = new Point(10, 10);
Point p2 = p1;
// Print both points.
p1.Display();
p2.Display();
// Change p1.X and print again. p2.X is not changed.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display();
p2.Display();
}
Here you have created a variable of type Point (named p1) that is then assigned to another
Point (p2). Because Point is a value type, you have two copies of the MyPoint type on the stack, each
of which can be independently manipulated. Therefore, when you change the value of p1.X, the
value of p2.X is unaffected. Figure 4-10 shows the output once this method is called from Main().
Figure 4-10. Assignment of value types results in a verbatim copy of each field.
In stark contrast to value types, when you apply the assignment operator to reference types
(meaning all class instances), you are redirecting what the reference variable points to in memory.
To illustrate, create a new class type named PointRef that has the exact same members as the Point
structures, beyond renaming the constructor to match the class name:
// Classes are always reference types.
class PointRef
{
// Same members as the Point structure.
// Be sure to change your constructor name to PointRef!
public PointRef(int XPos, int YPos)
{
X = XPos;
Y = YPos;
}
}
Now, make use of your PointRef type within the following new method (note the code is identical
to the ValueTypeAssignment() method). Assuming you have called this new method within
Main(), your output should look like that in Figure 4-11.
static void ReferenceTypeAssignment()
{
Console.WriteLine("Assigning reference types\n");
PointRef p1 = new PointRef(10, 10);
PointRef p2 = p1;
// Print both point refs.
p1.Display();
p2.Display();
// Change p1.X and print again.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display();
p2.Display();
}
Figure 4-11. Assignment of reference types copies the reference.
In this case, you have two references pointing to the same object on the managed heap. Therefore,
when you change the value of X using the p2 reference, p1.X reports the same value.
Value Types Containing Reference Types
Now that you have a better feeling for the core differences between value types and reference types,
let’s examine a more complex example. Assume you have the following reference (class) type that
maintains an informational string that can be set using a custom constructor:
class ShapeInfo
{
public string infoString;
public ShapeInfo(string info)
{ infoString = info; }
}
Now assume that you want to contain a variable of this class type within a value type named
Rectangle. To allow the caller to set the value of the inner ShapeInfo member variable, you also provide
a custom constructor. Here is the complete definition of the Rectangle type:
struct Rectangle
{
// The Rectangle structure contains a reference type member.
public ShapeInfo rectInfo;
public int rectTop, rectLeft, rectBottom, rectRight;
public Rectangle(string info, int top, int left, int bottom, int right)
{
rectInfo = new ShapeInfo(info);
rectTop = top; rectBottom = bottom;
rectLeft = left; rectRight = right;
}
public void Display()
{
Console.WriteLine("String = {0}, Top = {1}, Bottom = {2}," +
"Left = {3}, Right = {4}",
rectInfo.infoString, rectTop, rectBottom, rectLeft, rectRight);
}
}
At this point, you have contained a reference type within a value type. The million-dollar question
now becomes, What happens if you assign one Rectangle variable to another? Given what you
already know about value types, you would be correct in assuming that the integer data (which is
indeed a structure) should be an independent entity for each Rectangle variable. But what about
the internal reference type? Will the object’s state be fully copied, or will the reference to that object
be copied? To answer this question, define the following method and invoke it from Main(). Check
static void ValueTypeContainingRefType()
{
// Create the first Rectangle.
Console.WriteLine("-> Creating r1");
Rectangle r1 = new Rectangle("First Rect", 10, 10, 50, 50);
// Now assign a new Rectangle to r1.
Console.WriteLine("-> Assigning r2 to r1");
Rectangle r2 = r1;
// Change some values of r2.
Console.WriteLine("-> Changing values of r2");
r2.rectInfo.infoString = "This is new info!";
r2.rectBottom = 4444;
// Print values of both rectangles.
r1.Display();
r2.Display();
}
As you can see, when you change the value of the informational string using the r2 reference,
the r1 reference displays the same value. By default, when a value type contains other reference
types, assignment results in a copy of the references. In this way, you have two independent structures,
each of which contains a reference pointing to the same object in memory (i.e., a “shallow
copy”). When you want to perform a “deep copy,” where the state of internal references is fully
copied into a new object, one approach is to implement the ICloneable interface (as you will do in
Chapter 9).
nSource Code The ValueAndReferenceTypes project is located under the Chapter 4 subdirectory.
Passing Reference Types by Value
Reference types or value types can obviously be passed as parameters to type members. However,
passing a reference type (e.g., a class) by reference is quite different from passing it by value. To
understand the distinction, assume you have a simple Person class defined in a new Console Application
project named RefTypeValTypeParams, defined as follows:
class Person
{
public string personName;
public int personAge;
// Constructors.
public Person(string name, int age)
{
personName = name;
personAge = age;
}
public Person(){}
public void Display()
{
Console.WriteLine("Name: {0}, Age: {1}", personName, personAge);
}
}
Now, what if you create a method that allows the caller to send in the Person type by value
(note the lack of parameter modifiers):
static void SendAPersonByValue(Person p)
{
// Change the age of "p"?
p.personAge = 99;
// Will the caller see this reassignment?
p = new Person("Nikki", 99);
}
Notice how the SendAPersonByValue() method attempts to reassign the incoming Person
reference to a new object as well as change some state data. Now let’s test this method using the
following Main() method:
static void Main(string[] args)
{
// Passing ref-types by value.
Console.WriteLine("***** Passing Person object by value *****");
Person fred = new Person("Fred", 12);
Console.WriteLine("\nBefore by value call, Person is:");
fred.Display();
SendAPersonByValue(fred);
Console.WriteLine("\nAfter by value call, Person is:");
fred. Display();
Console.ReadLine();
}
Figure 4-13. Passing reference types by value locks the reference in place.
As you can see, the value of personAge has been modified. This behavior seems to fly in the face
of what it means to pass a parameter “by value.” Given that you were able to change the state of the
incoming Person, what was copied? The answer: a copy of the reference to the caller’s object. Therefore,
as the SendAPersonByValue() method is pointing to the same object as the caller, it is possible
to alter the object’s state data. What is not possible is to reassign what the reference is pointing to.
Passing Reference Types by Reference
Now assume you have a SendAPersonByReference() method, which passes a reference type by reference
(note the ref parameter modifier):
static void SendAPersonByReference(ref Person p)
{
// Change some data of "p".
p.personAge = 555;
// "p" is now pointing to a new object on the heap!
p = new Person("Nikki", 999);
}
As you might expect, this allows complete flexibility of how the callee is able to manipulate the
incoming parameter. Not only can the callee change the state of the object, but if it so chooses, it
may also reassign the reference to a new Person type. Now ponder the following updated Main()
method and check Figure 4-14 for output:
static void Main(string[] args)
{
// Passing ref-types by ref.
Console.WriteLine("\n***** Passing Person object by reference *****");
Person mel = new Person("Mel", 23);
Console.WriteLine("Before by ref call, Person is:");
mel.Display();
SendAPersonByReference(ref mel);
Console.WriteLine("After by ref call, Person is:");
mel.Display();
Console.ReadLine();
}
Figure 4-14. Passing reference types by reference allows the reference to be redirected.
As you can see, an object named Mel returns after the call as a type named Nikki, as the
method was able to change what the incoming reference pointed to in memory. The golden rule to
keep in mind when passing reference types:
• If a reference type is passed by reference, the callee may change the values of the object’s
state data as well as the object it is referencing.
• If a reference type is passed by value, the callee may change the values of the object’s state
data but not the object it is referencing.
nSource Code The RefTypeValTypeParams project is located under the Chapter 4 subdirectory.
Value and Reference Types: Final Details
To wrap up this topic, consider the information in Table 4-3, which summarizes the core distinctions
between value types and reference types.
How is a variable represented? Value type variables Reference type variables are
are local copies. pointing to the memory
occupied by the allocated
instance.
What is the base type? Must derive from Can derive from any other
System.ValueType. type (except System.
ValueType), as long as that
type is not “sealed” (more
details on this in Chapter 6).
Can this type function as a No. Value types are always Yes. If the type is not sealed,
base to other types? sealed and cannot be it may function as a base to
extended. other types.
What is the default parameter Variables are passed by value Variables are passed by
passing behavior? (i.e., a copy of the variable is reference (i.e., the address
passed into the called function). of the variable is passed into
the called function).
Can this type override No. Value types are never placed Yes, indirectly (more details
System.Object.Finalize()? onto the heap and therefore do on this in Chapter 8).
not need to be finalized.
Can I define constructors Yes, but the default constructor But of course!
for this type? is reserved (i.e., your custom
constructors must all have
arguments).
When do variables of this When they fall out of the When the object is garbage
type die? defining scope. collected.
Despite their differences, value types and reference types both have the ability to implement
interfaces and may support any number of fields, methods, overloaded operators, constants, properties,
and events.
object-oriented programming.We will examine a number of topics that assume you have a background in objectoriented
programming. If this is not the case, you may wish to reread this section once you have completed
Unlike arrays, strings, or enumerations, C# structures do not have an identically named representation
in the .NET library (that is, there is no System.Structure class), but are implicitly derived from
System.ValueType. Simply put, the role of System.ValueType is to ensure that the derived type (e.g.,
any structure) is allocated on the stack rather than the garbage collected heap.
Functionally, the only purpose of System.ValueType is to “override” the virtual methods defined
by System.Object to use value-based, versus reference-based, semantics. As you may know, overriding
is the process of changing the implementation of a virtual (or possibly abstract) method defined
within a base class. The base class of ValueType is System.Object. In fact, the instance methods
defined by System.ValueType are identical to those of System.Object:
// Structures and enumerations extend System.ValueType.
public abstract class ValueType : object
{
public virtual bool Equals(object obj);
public virtual int GetHashCode();
public Type GetType();
public virtual string ToString();
}
Given the fact that value types are using value-based semantics, the lifetime of a structure
(which includes all numerical data types [int, float, etc.], as well as any enum or custom structure)
is very predictable. When a structure variable falls out of the defining scope, it is removed from
memory immediately:
// Local structures are popped off
// the stack when a method returns.
static void LocalValueTypes()
{
// Recall! "int" is really a System.Int32 structure.
int i = 0;
// Recall! Point is a structure type.
Point p = new Point();
} // "i" and "p" popped off the stack here!
Value Types, References Types, and the Assignment Operator
When you assign one value type to another, a member-by-member copy of the field data is
achieved. In the case of a simple data type such as System.Int32, the only member to copy is the
numerical value. However, in the case of our Point, the X and Y values are copied into the new
structure variable. To illustrate, create a new Console Application project named
ValueAndReferenceTypes and copy your previous Point definition into your new namespace.
Now, add the following method to your Program type:
// Assigning two intrinsic value types results in
// two independent variables on the stack.
static void ValueTypeAssignment()
{
Console.WriteLine("Assigning value types\n");
Point p1 = new Point(10, 10);
Point p2 = p1;
// Print both points.
p1.Display();
p2.Display();
// Change p1.X and print again. p2.X is not changed.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display();
p2.Display();
}
Here you have created a variable of type Point (named p1) that is then assigned to another
Point (p2). Because Point is a value type, you have two copies of the MyPoint type on the stack, each
of which can be independently manipulated. Therefore, when you change the value of p1.X, the
value of p2.X is unaffected. Figure 4-10 shows the output once this method is called from Main().
Figure 4-10. Assignment of value types results in a verbatim copy of each field.
In stark contrast to value types, when you apply the assignment operator to reference types
(meaning all class instances), you are redirecting what the reference variable points to in memory.
To illustrate, create a new class type named PointRef that has the exact same members as the Point
structures, beyond renaming the constructor to match the class name:
// Classes are always reference types.
class PointRef
{
// Same members as the Point structure.
// Be sure to change your constructor name to PointRef!
public PointRef(int XPos, int YPos)
{
X = XPos;
Y = YPos;
}
}
Now, make use of your PointRef type within the following new method (note the code is identical
to the ValueTypeAssignment() method). Assuming you have called this new method within
Main(), your output should look like that in Figure 4-11.
static void ReferenceTypeAssignment()
{
Console.WriteLine("Assigning reference types\n");
PointRef p1 = new PointRef(10, 10);
PointRef p2 = p1;
// Print both point refs.
p1.Display();
p2.Display();
// Change p1.X and print again.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display();
p2.Display();
}
Figure 4-11. Assignment of reference types copies the reference.
In this case, you have two references pointing to the same object on the managed heap. Therefore,
when you change the value of X using the p2 reference, p1.X reports the same value.
Value Types Containing Reference Types
Now that you have a better feeling for the core differences between value types and reference types,
let’s examine a more complex example. Assume you have the following reference (class) type that
maintains an informational string that can be set using a custom constructor:
class ShapeInfo
{
public string infoString;
public ShapeInfo(string info)
{ infoString = info; }
}
Now assume that you want to contain a variable of this class type within a value type named
Rectangle. To allow the caller to set the value of the inner ShapeInfo member variable, you also provide
a custom constructor. Here is the complete definition of the Rectangle type:
struct Rectangle
{
// The Rectangle structure contains a reference type member.
public ShapeInfo rectInfo;
public int rectTop, rectLeft, rectBottom, rectRight;
public Rectangle(string info, int top, int left, int bottom, int right)
{
rectInfo = new ShapeInfo(info);
rectTop = top; rectBottom = bottom;
rectLeft = left; rectRight = right;
}
public void Display()
{
Console.WriteLine("String = {0}, Top = {1}, Bottom = {2}," +
"Left = {3}, Right = {4}",
rectInfo.infoString, rectTop, rectBottom, rectLeft, rectRight);
}
}
At this point, you have contained a reference type within a value type. The million-dollar question
now becomes, What happens if you assign one Rectangle variable to another? Given what you
already know about value types, you would be correct in assuming that the integer data (which is
indeed a structure) should be an independent entity for each Rectangle variable. But what about
the internal reference type? Will the object’s state be fully copied, or will the reference to that object
be copied? To answer this question, define the following method and invoke it from Main(). Check
static void ValueTypeContainingRefType()
{
// Create the first Rectangle.
Console.WriteLine("-> Creating r1");
Rectangle r1 = new Rectangle("First Rect", 10, 10, 50, 50);
// Now assign a new Rectangle to r1.
Console.WriteLine("-> Assigning r2 to r1");
Rectangle r2 = r1;
// Change some values of r2.
Console.WriteLine("-> Changing values of r2");
r2.rectInfo.infoString = "This is new info!";
r2.rectBottom = 4444;
// Print values of both rectangles.
r1.Display();
r2.Display();
}
As you can see, when you change the value of the informational string using the r2 reference,
the r1 reference displays the same value. By default, when a value type contains other reference
types, assignment results in a copy of the references. In this way, you have two independent structures,
each of which contains a reference pointing to the same object in memory (i.e., a “shallow
copy”). When you want to perform a “deep copy,” where the state of internal references is fully
copied into a new object, one approach is to implement the ICloneable interface (as you will do in
Chapter 9).
nSource Code The ValueAndReferenceTypes project is located under the Chapter 4 subdirectory.
Passing Reference Types by Value
Reference types or value types can obviously be passed as parameters to type members. However,
passing a reference type (e.g., a class) by reference is quite different from passing it by value. To
understand the distinction, assume you have a simple Person class defined in a new Console Application
project named RefTypeValTypeParams, defined as follows:
class Person
{
public string personName;
public int personAge;
// Constructors.
public Person(string name, int age)
{
personName = name;
personAge = age;
}
public Person(){}
public void Display()
{
Console.WriteLine("Name: {0}, Age: {1}", personName, personAge);
}
}
Now, what if you create a method that allows the caller to send in the Person type by value
(note the lack of parameter modifiers):
static void SendAPersonByValue(Person p)
{
// Change the age of "p"?
p.personAge = 99;
// Will the caller see this reassignment?
p = new Person("Nikki", 99);
}
Notice how the SendAPersonByValue() method attempts to reassign the incoming Person
reference to a new object as well as change some state data. Now let’s test this method using the
following Main() method:
static void Main(string[] args)
{
// Passing ref-types by value.
Console.WriteLine("***** Passing Person object by value *****");
Person fred = new Person("Fred", 12);
Console.WriteLine("\nBefore by value call, Person is:");
fred.Display();
SendAPersonByValue(fred);
Console.WriteLine("\nAfter by value call, Person is:");
fred. Display();
Console.ReadLine();
}
Figure 4-13. Passing reference types by value locks the reference in place.
As you can see, the value of personAge has been modified. This behavior seems to fly in the face
of what it means to pass a parameter “by value.” Given that you were able to change the state of the
incoming Person, what was copied? The answer: a copy of the reference to the caller’s object. Therefore,
as the SendAPersonByValue() method is pointing to the same object as the caller, it is possible
to alter the object’s state data. What is not possible is to reassign what the reference is pointing to.
Passing Reference Types by Reference
Now assume you have a SendAPersonByReference() method, which passes a reference type by reference
(note the ref parameter modifier):
static void SendAPersonByReference(ref Person p)
{
// Change some data of "p".
p.personAge = 555;
// "p" is now pointing to a new object on the heap!
p = new Person("Nikki", 999);
}
As you might expect, this allows complete flexibility of how the callee is able to manipulate the
incoming parameter. Not only can the callee change the state of the object, but if it so chooses, it
may also reassign the reference to a new Person type. Now ponder the following updated Main()
method and check Figure 4-14 for output:
static void Main(string[] args)
{
// Passing ref-types by ref.
Console.WriteLine("\n***** Passing Person object by reference *****");
Person mel = new Person("Mel", 23);
Console.WriteLine("Before by ref call, Person is:");
mel.Display();
SendAPersonByReference(ref mel);
Console.WriteLine("After by ref call, Person is:");
mel.Display();
Console.ReadLine();
}
Figure 4-14. Passing reference types by reference allows the reference to be redirected.
As you can see, an object named Mel returns after the call as a type named Nikki, as the
method was able to change what the incoming reference pointed to in memory. The golden rule to
keep in mind when passing reference types:
• If a reference type is passed by reference, the callee may change the values of the object’s
state data as well as the object it is referencing.
• If a reference type is passed by value, the callee may change the values of the object’s state
data but not the object it is referencing.
nSource Code The RefTypeValTypeParams project is located under the Chapter 4 subdirectory.
Value and Reference Types: Final Details
To wrap up this topic, consider the information in Table 4-3, which summarizes the core distinctions
between value types and reference types.
How is a variable represented? Value type variables Reference type variables are
are local copies. pointing to the memory
occupied by the allocated
instance.
What is the base type? Must derive from Can derive from any other
System.ValueType. type (except System.
ValueType), as long as that
type is not “sealed” (more
details on this in Chapter 6).
Can this type function as a No. Value types are always Yes. If the type is not sealed,
base to other types? sealed and cannot be it may function as a base to
extended. other types.
What is the default parameter Variables are passed by value Variables are passed by
passing behavior? (i.e., a copy of the variable is reference (i.e., the address
passed into the called function). of the variable is passed into
the called function).
Can this type override No. Value types are never placed Yes, indirectly (more details
System.Object.Finalize()? onto the heap and therefore do on this in Chapter 8).
not need to be finalized.
Can I define constructors Yes, but the default constructor But of course!
for this type? is reserved (i.e., your custom
constructors must all have
arguments).
When do variables of this When they fall out of the When the object is garbage
type die? defining scope. collected.
Despite their differences, value types and reference types both have the ability to implement
interfaces and may support any number of fields, methods, overloaded operators, constants, properties,
and events.
Understanding the Structure Type
Now that you understand the role of enumeration types, let’s examine the use of .NET structures (or
simply structs). Structure types are well suited for modeling mathematical, geometrical, and other
“atomic” entities in your application. A structure (like an enumeration) is a user-defined type; however,
structures are not simply a collection of name/value pairs. Rather, structures are types that can
contain any number of data fields and members that operate on these fields.
Furthermore, structures can define constructors, can implement interfaces, and can contain
any number of properties, methods, events, and overloaded operators. (If some of these terms are
unfamiliar at this point, don’t fret. All of these topics are fully examined in chapters to come.)
nNote If you have a background in OOP, you can think of a structure as a “lightweight class type,” given that
structures provide a way to define a type that supports encapsulation, but cannot be used to build a family of
related types (as structures are implicitly sealed). When you need to build a family of related types through inheritance,
you will need to make use of class types.
On the surface, the process of defining and using structures is very simple, but as they say, the
devil is in the details. To begin understanding the basics of structure types, create a new project
named FunWithStructures. In C#, structures are created using the struct keyword. Define a new
structure named Point, which defines two member variables of type int and a set of methods to
interact with said data:
struct Point
{
// Fields of the structure.
public int X;
public int Y;
// Add 1 to the (X, Y) position.
public void Increment()
{
X++; Y++;
}
// Subtract 1 from the (X, Y) position.
public void Decrement()
{
X--; Y--;
}
// Display the current position.
public void Display()
126 CHAPTER 4 n CORE C# PROGRAMMING CONSTRUCTS, PART II
{
Console.WriteLine("X = {0}, Y = {1}", X, Y);
}
}
Here, we have defined our two integer data types (X and Y) using the public keyword, which is
an access control modifier (full details in the next chapter). Declaring data with the public keyword
ensures the caller has direct access to the data from a given Point variable (via the dot operator).
nNote It is typically considered bad style to define public data within a class or structure. Rather, you will want to
define private data, which can be accessed and changed using public properties. These details will be examined in
Chapter 5.
Here is a Main() method that takes our Point type out for a test drive. Figure 4-9 shows the program’s
output.
static void Main(string[] args)
{
Console.WriteLine("***** A First Look at Structures *****");
// Create an initial Point.
Point myPoint;
myPoint.X = 349;
myPoint.Y = 76;
myPoint.Display();
// Adjust the X and Y values.
myPoint.Increment();
myPoint.Display();
Console.ReadLine();
}
Creating Structure Variables
When you wish to create a structure variable, you have a variety of options. Here, we simply create a
Point variable and assign each piece of public field data before invoking its members. If we do not
assign each piece of public field data (X and Y in our case) before making use of the structure, we
will receive a compiler error:
// Error! Did not assign Y value.
Point p1;
p1.X = 10;
p1.Display();
// OK! Both fields assigned before use.
Point p2;
p2.X = 10;
p2.Y = 10;
p2.Display();
As an alternative, we can create structure variables using the C# new keyword, which will invoke
the structure’s default constructor. By definition, a default constructor takes any input parameters.
The benefit of invoking the default constructor of a structure is that each piece of field data is automatically
set to its default value:
// Set all fields to default values
// using the default constructor.
Point p1 = new Point();
// Prints X=0,Y=0
p1.Display();
It is also possible to design a structure with a custom constructor. This allows you to specify the
values of field data upon variable creation, rather than having to set each data member field by
field. Chapter 5 will provide a detailed examination of constructors; however, to illustrate, update
the Point structure with the following code:
struct Point
{
// Fields of the structure.
public int X;
public int Y;
// A custom constructor.
public Point(int XPos, int YPos)
{
X = XPos;
Y = YPos;
}
...
}
With this, we could now create Point types as follows:
// Call custom constructor.
Point p2 = new Point(50, 60);
// Prints X=50,Y=60
p2.Display();
As mentioned, working with structures on the surface is quite simple. However, to deepen your
understanding of this type, we need to explore the distinction between a .NET value type and a
.NET reference type.
simply structs). Structure types are well suited for modeling mathematical, geometrical, and other
“atomic” entities in your application. A structure (like an enumeration) is a user-defined type; however,
structures are not simply a collection of name/value pairs. Rather, structures are types that can
contain any number of data fields and members that operate on these fields.
Furthermore, structures can define constructors, can implement interfaces, and can contain
any number of properties, methods, events, and overloaded operators. (If some of these terms are
unfamiliar at this point, don’t fret. All of these topics are fully examined in chapters to come.)
nNote If you have a background in OOP, you can think of a structure as a “lightweight class type,” given that
structures provide a way to define a type that supports encapsulation, but cannot be used to build a family of
related types (as structures are implicitly sealed). When you need to build a family of related types through inheritance,
you will need to make use of class types.
On the surface, the process of defining and using structures is very simple, but as they say, the
devil is in the details. To begin understanding the basics of structure types, create a new project
named FunWithStructures. In C#, structures are created using the struct keyword. Define a new
structure named Point, which defines two member variables of type int and a set of methods to
interact with said data:
struct Point
{
// Fields of the structure.
public int X;
public int Y;
// Add 1 to the (X, Y) position.
public void Increment()
{
X++; Y++;
}
// Subtract 1 from the (X, Y) position.
public void Decrement()
{
X--; Y--;
}
// Display the current position.
public void Display()
126 CHAPTER 4 n CORE C# PROGRAMMING CONSTRUCTS, PART II
{
Console.WriteLine("X = {0}, Y = {1}", X, Y);
}
}
Here, we have defined our two integer data types (X and Y) using the public keyword, which is
an access control modifier (full details in the next chapter). Declaring data with the public keyword
ensures the caller has direct access to the data from a given Point variable (via the dot operator).
nNote It is typically considered bad style to define public data within a class or structure. Rather, you will want to
define private data, which can be accessed and changed using public properties. These details will be examined in
Chapter 5.
Here is a Main() method that takes our Point type out for a test drive. Figure 4-9 shows the program’s
output.
static void Main(string[] args)
{
Console.WriteLine("***** A First Look at Structures *****");
// Create an initial Point.
Point myPoint;
myPoint.X = 349;
myPoint.Y = 76;
myPoint.Display();
// Adjust the X and Y values.
myPoint.Increment();
myPoint.Display();
Console.ReadLine();
}
Creating Structure Variables
When you wish to create a structure variable, you have a variety of options. Here, we simply create a
Point variable and assign each piece of public field data before invoking its members. If we do not
assign each piece of public field data (X and Y in our case) before making use of the structure, we
will receive a compiler error:
// Error! Did not assign Y value.
Point p1;
p1.X = 10;
p1.Display();
// OK! Both fields assigned before use.
Point p2;
p2.X = 10;
p2.Y = 10;
p2.Display();
As an alternative, we can create structure variables using the C# new keyword, which will invoke
the structure’s default constructor. By definition, a default constructor takes any input parameters.
The benefit of invoking the default constructor of a structure is that each piece of field data is automatically
set to its default value:
// Set all fields to default values
// using the default constructor.
Point p1 = new Point();
// Prints X=0,Y=0
p1.Display();
It is also possible to design a structure with a custom constructor. This allows you to specify the
values of field data upon variable creation, rather than having to set each data member field by
field. Chapter 5 will provide a detailed examination of constructors; however, to illustrate, update
the Point structure with the following code:
struct Point
{
// Fields of the structure.
public int X;
public int Y;
// A custom constructor.
public Point(int XPos, int YPos)
{
X = XPos;
Y = YPos;
}
...
}
With this, we could now create Point types as follows:
// Call custom constructor.
Point p2 = new Point(50, 60);
// Prints X=50,Y=60
p2.Display();
As mentioned, working with structures on the surface is quite simple. However, to deepen your
understanding of this type, we need to explore the distinction between a .NET value type and a
.NET reference type.
The System.Enum Type
The interesting thing about .NET enumerations is that they gain functionality from the System.Enum
class type. This class defines a number of methods that allow you to interrogate and transform a
given enumeration. One helpful method is the static Enum.GetUnderlyingType(), which as the name
implies returns the data type used to store the values of the enumerated type (System.Byte in the
case of the current EmpType declaration).
static void Main(string[] args)
{
Console.WriteLine("**** Fun with Enums *****");
// Make a contractor type.
EmpType emp = EmpType.Contractor;
AskForBonus(emp);
// Print storage for the enum.
Console.WriteLine("EmpType uses a {0} for storage",
Enum.GetUnderlyingType(emp.GetType()));
Console.ReadLine();
}
If you were to consult the Visual Studio 2008 object browser, you would be able to verify that
the Enum.GetUnderlyingType() method requires you to pass in a System.Type as the first parameter.
As fully examined in Chapter 16, Type represents the metadata description of a given .NET entity.
One possible way to obtain metadata (as shown previously) is to use the GetType() method,
which is common to all types in the .NET base class libraries. Another approach is to make use of
the C# typeof operator. One benefit of doing so is that you do not need to have a variable of the
entity you wish to obtain a metadata description of:
// This time use typeof to extract a Type.
Console.WriteLine("EmpType uses a {0} for storage",
Enum.GetUnderlyingType(typeof(EmpType)));
class type. This class defines a number of methods that allow you to interrogate and transform a
given enumeration. One helpful method is the static Enum.GetUnderlyingType(), which as the name
implies returns the data type used to store the values of the enumerated type (System.Byte in the
case of the current EmpType declaration).
static void Main(string[] args)
{
Console.WriteLine("**** Fun with Enums *****");
// Make a contractor type.
EmpType emp = EmpType.Contractor;
AskForBonus(emp);
// Print storage for the enum.
Console.WriteLine("EmpType uses a {0} for storage",
Enum.GetUnderlyingType(emp.GetType()));
Console.ReadLine();
}
If you were to consult the Visual Studio 2008 object browser, you would be able to verify that
the Enum.GetUnderlyingType() method requires you to pass in a System.Type as the first parameter.
As fully examined in Chapter 16, Type represents the metadata description of a given .NET entity.
One possible way to obtain metadata (as shown previously) is to use the GetType() method,
which is common to all types in the .NET base class libraries. Another approach is to make use of
the C# typeof operator. One benefit of doing so is that you do not need to have a variable of the
entity you wish to obtain a metadata description of:
// This time use typeof to extract a Type.
Console.WriteLine("EmpType uses a {0} for storage",
Enum.GetUnderlyingType(typeof(EmpType)));
Subscribe to:
Posts (Atom)