Thursday, October 22, 2009

Understanding the static Keyword

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++;

No comments:

Post a Comment