Classes

for RuBoard

In this section we carefully examine the C# class , which is fundamental to programming in C#. For illustration we introduce two classes, Customer and Hotel , which will be elaborated in a case study that is used throughout the book. We will introduce the case study itself in Chapter 4.

If you are a Java programmer, you will find the C# class to be quite familiar, and you should be able to skim this section. C++ programmers must read much more carefully. C# differs from C++ with respect to object instantiation, assignment, and destruction. Our pace is somewhat more leisurely in this section, because classes are so fundamental to programming in C#.

Classes as Structured Data

C# defines primitive data types that are built into the language. Data types, such as int and decimal , can be used to represent simple data. C# provides the class mechanism to represent more complex forms of data. Through a class, you can build up structured data out of simpler elements, which are called data members , or fields . (See TestCustomer\Step1 .)

 // Customer.cs - Step 1  public class Customer  {     public int CustomerId;     public string FirstName;     public string LastName;     public string EmailAddress;     public Customer(string first, string last, string email)     {        FirstName = first;        LastName = last;        EmailAddress = email;     }  } 

Customer is now a new data type. A customer has a CustomerId , a FirstName , a LastName , and an EmailAddress .

Classes and Objects

A class represents a "kind of," or type of, data. It is analogous to the built-in types like int and decimal . A class can be thought of as a template from which individual instances can be created. An instance of a class is called an object. Just as you can have several individual integers that are instances of int , you can have several customers that are instances of Customer . The fields, such as CustomerId and FirstName in our example, are sometimes also called instance variables .

References

There is a fundamental distinction between the primitive data types and the extended data types that can be created using classes. When you declare a variable of a primitive data, you are allocating memory and creating the instance.

 int x; // 4 bytes of memory have been allocated 

When you declare a variable of a class type (an object reference ), you are only obtaining memory for a reference to an object of the class type. No memory is allocated for the object itself, which may be quite large. This behavior is very different from that of C++, where declaring an object in this way causes an instance to be created, using the default constructor. The behavior is identical to what happens in Java.

 Customer cust; // cust is a reference to a Customer object                 // The object itself does not yet exist 
Constructors

Through a constructor, you can initialize individual objects in any way you wish. Besides initializing instance data, you can perform other appropriate initializations (e.g., open a file).

A constructor is like a special method that is automatically called when an object is created via the new keyword. A constructor

  • has no return type

  • has the same name as the class

  • should usually have public access

  • may take parameters, which are passed when invoking new

In the calling program, you use new to instantiate object instances, and you pass desired values as parameters.

Default Constructor

If you do not define a constructor in your class, C# will implicitly create one for you. It is called the default constructor and takes no arguments. The default constructor will assign instance data, using any assignments in the class definition. Fields without an initializer are assigned default values (0 for numerical data types, empty string for string , and so on). The default constructor is called when an object instance is created with new and no parameters. If you provide code for any constructor in your class, you must explicitly define a default constructor with no arguments, if you want one.

Instantiating and Using an Object

You instantiate an object by the new operator, which will cause a constructor to be invoked.

 cust = new Customer("Rocket",                      "Squirrel",                      "rocky@frosbitefalls.com");  // Customer object now exists and cust is a reference to it 

Once an object exists, you work with it, including accessing its fields and methods . Our simple Customer class at this point has no methods, only four fields. You access fields and methods using a dot.

  cust.CustomerId  = 1; // all fields have now been assigned 

TestCustomer\Step0 provides a simple test program to exercise the Customer class. Note that an unassigned field of a class receives a default value, such as 0, when an object is instantiated .

Assigning Object References

TestCustomer\Step1 provides a more complete test program to exercise the Customer class. Two object instances are created, an assignment is made of one object reference to another, and a field is assigned a value.

 // TestCustomer.cs  using System;  public class TestCustomer  {     public static void Main()     {        Customer cust1, cust2;        cust1 = new Customer("Rocket",                             "Squirrel",                             "rocky@frosbitefalls.com");        cust1.CustomerId = 1;        cust2 = new Customer("Bullwinkle",                             "Moose,                             "moose@wossamotta.edu");        cust2.CustomerId = 2;        ShowCustomer("cust1", cust1);        ShowCustomer("cust2", cust2);        cust1 = cust2;        cust1.EmailAddress = "bob@podunk.edu";        ShowCustomer("cust1", cust1);        ShowCustomer("cust2", cust2);     }     private static void ShowCustomer(string label,                                      Customer cust)     {        Console.WriteLine("- - - - {0} - - - -", label);        Console.WriteLine("CustomerId = {0}",                          cust.CustomerId);        Console.WriteLine("FirstName = {0}", cust.FirstName);        Console.WriteLine("LastName = {0}", cust.LastName);        Console.WriteLine("EmailAddress = {0}",                          cust.EmailAddress);     }  } 

Figure 3-1 shows the object references cust1 and cust2 and the data they refer to after the objects have been instantiated and the CustomerId field has been assigned.

Figure 3-1. Two object references and the data they refer to.

graphics/03fig01.gif

When you assign an object variable, you are assigning only the reference; there is no copying of data . [2] Figure 3-2 shows both object references and their data after the assignment:

[2] C and C++ programmers will recognize assignment of references as similar to assignment of pointers.

Figure 3-2. Two references refer to the same data.

graphics/03fig02.gif

 cust1 = cust2; // cust1 and cust2 now refer to same object 

Now when you assign a new value a field of one object,

 cust1.EmailAddress = "bob@podunk.edu"; 

you will see the same data through both object references. Here is the output from running TestCustomer\Step1 .

 - - - - cust1 - - - - CustomerId = 1  FirstName = Rocket  LastName = Squirrel  EmailAddress = rocky@frosbitefalls.com  - - - - cust2 - - - - CustomerId = 2  FirstName = Bullwinkle  LastName = Moose  EmailAddress = moose@wossamotta.edu  - - - - cust1 - - - - CustomerId = 2  FirstName = Bullwinkle  LastName = Moose  EmailAddress = bob@podunk.edu  cust1 1  "Rocket"  "Squirrel"  "rocky@frosbitefalls.com"  cust2 2  "Bullwinkle"  "Moose"  "moose@wossamotta.edu"  - - - - cust2 - - - - CustomerId = 2  FirstName = Bullwinkle  LastName = Moose  EmailAddress = bob@podunk.edu 
Garbage Collection

Through the assignment of a reference, an object may become orphaned. Objects may also be orphaned when they pass out of scope. Such an orphan object (or "garbage") takes up memory in the computer, which can now never be referenced. In Figure 3-2 the customer with CustomerId of 1 is now garbage.

The Common Language Runtime automatically reclaims the memory of unreferenced objects. This process is known as garbage collection . Garbage collection takes up some execution time, but it is a great convenience for programmers, helping to avoid a common program error known as a memory leak . Garbage collection is discussed in more detail in Chapter 8.

Methods

Typically, a class will specify behavior as well as data. A class encapsulates data and behavior in a single entity. A method specifies the behavior and consists of

  • An access specifier , typically public or private

  • A return type (can be void if the method does not return data)

  • A method name, which can be any legal C# identifier

  • A parameter list, enclosed by parentheses, which specifies data that is passed to the method (can be empty if no data is passed)

  • A method body, enclosed by curly braces, which contains the C# code that the method will execute

 public void RaisePrice(decimal amount)  {     rate += amount;  } 

In this example the return type is void (no data is passed back), the method name is RaisePrice , the parameter list consists of a single parameter of type decimal , and the body contains one line of code that increments the member variable rate by the value that is passed in.

RaisePrice is a method in the Hotel class. The initial version of the Hotel class with a simple test program is in the folder TestHotel\Step1 .

Public and Private

Fields and methods of a C# class can be specified as public or private . Normally, you declare fields as private . A private field can be accessed only from within the class, not from outside.

 public class Hotel  {  private string city;   private string name;   private int number = 50; // legal in C#   private decimal rate;  ... 

Note that in C# you can initialize fields when they are declared. This kind of initialization is not legal in C++.

Methods may be declared as either public or private . Public methods are called from outside the class and are used to perform calculations and to manipulate the private data. You may also provide public "accessor" methods to provide access to private fields.

 ...  public decimal GetRate()  {     return rate;  }  public void SetRate(decimal val)  {     rate = val;  }  ... 

You may also have private methods, which can be thought of as "helper functions" for use within the class. Rather than duplicating code in several places, you may create a private method, which will be called wherever it is needed. An example is the ShowHotel method in TestHotel.cs .

This

Sometimes it is convenient within code for a method to be able to access the current object reference. C#, like C++, defines a keyword this , which is a special variable that always refers to the current object instance. With this you can then refer to the instance variables. The Hotel class has a constructor to initialize its instance data with values passed as parameters. We can make use of the same names for parameters and fields and remove ambiguity by using the this variable. Here is the code for the constructor:

 public Hotel(string city, string name, int number,               decimal rate)  {  this  .city = city;  this  .name = name;  this  .number = number;  this  .rate = rate;  } 
Sample Program

The program TestHotel\Step1 illustrates all the features we have discussed so far. Here is the class definition:

 // Hotel.cs - Step 1  public class Hotel  {     private string city;     private string name;     private int number = 50;   // legal in C#     private decimal rate;     public Hotel(string city, string name, int number,                  decimal rate)     {        this.city = city;        this.name = name;        this.number = number;        this.rate = rate;     }     public Hotel()     {     }     public string GetCity()     {        return city;     }     public string GetName()     {        return name;     }     public int GetNumber()     {        return number;     }     public void SetNumber(int val)     {        number = val;     }     public decimal GetRate()     {        return rate;     }     public void SetRate(decimal val)     {        rate = val;     }     public void RaisePrice(decimal amount)     {        rate += amount;     }  } 

Here is the test program:

 // Test.cs - Step 1  using System;  public class TestHotel  {     public static void Main()     {        Hotel generic = new Hotel();        ShowHotel(generic);        Hotel ritz = new Hotel("Atlanta", "Ritz", 100, 95m);        ShowHotel(ritz);        ritz.RaisePrice(50m);        ritz.SetNumber(125);        ShowHotel(ritz);     }     private static void ShowHotel(Hotel hotel)     {        Console.WriteLine("{0} {1}: number = {2}, rate = {3:C}",           hotel.GetCity(), hotel.GetName(),           hotel.GetNumber(), hotel.GetRate());     }  } 

Here is the output:

 : number = 50, rate = 
 : number = 50, rate = $0.00 Atlanta Ritz: number = 100, rate = $95.00 Atlanta Ritz: number = 125, rate = $145.00 
.00 Atlanta Ritz: number = 100, rate = .00 Atlanta Ritz: number = 125, rate = 5.00

Properties

The encapsulation principle leads us to typically store data in private fields and to provide access to this data through public accessor methods that allow us to set and get values. For example, in the Hotel class we provided a method GetCity to access the private field city . You don't need any special syntax; you can simply provide methods and call these methods what you want, typically GetXXX and SetXXX .

C# provides a special property syntax that simplifies user code. You can access a private field as if it were a public member. Here is an example of using a Number property of our Hotel class.

  ritz.Number  = 125;  Console.WriteLine("There are now {0} rooms",  ritz.Number  ); 

As you can see, the syntax using the property is a little more concise . Properties were popularized in Visual Basic and are now part of .NET and available in selected other .NET languages, such as C#. The program TestHotel\Step2, illustrates implementing and using several properties, City , Name , Number , and Rate . The first two properties are read-only (only get defined), and the other properties are read/write (both get and set ). It is also possible to have a write-only property (only set defined). Here is the code for the properties Name (read-only) and Number (read-write) in the second version of the Hotel class. Notice the syntax and the C# keyword value to indicate the new value of the field.

 // Hotel.cs - Step 2  public class Hotel  {     private string city;     private string name;     private int number;     private decimal rate;     ...  public string Name   {   get   {   return name;   }   }   public int Number   {   get   {   return number;   }   set   {   number = value;   }   }  ... 

Static Fields and Methods

In C# a field normally is assigned on a per-instance basis, with a unique value for each object instance of the class. Sometimes it is useful to have a single value associated with the entire class. This type of field is called a static field. Like instance data members, static data members can be either public or private . To access a public static member, you use the dot notation, but in place of an object reference before the dot you use the name of the class.

Static Methods

A method may also be declared static . A static method can be called without instantiating the class. An example we have already seen is the Main method in a class, which the runtime system is able to call without instantiating an object. The Main method must always be static.

You call a static method by using the dot notation, with the class name in front of the dot. Because you must call a static method without an instance, a static method can use only static data members and not instance data members.

Static methods may be declared public or private . A private static method, like other private methods, may be used as a helper function within a class, but not called from outside.

Sample Program

Our previous Customer class relied on the user of the class to assign a CustomerId for the customer. A better approach is to encapsulate assigning an id within the class itself, so that a unique id will be automatically generated every time a new Customer object is created. It is easy to implement such a scheme by using a static field nextCustId , which is used to assign an id. Every time the id is assigned, nextCustId is incremented. TestCustomer\Step2 demonstrates this solution and also illustrates the use of a static method. Here is the code defining the Customer class:

 // Customer.cs - Step 2  public class Customer  {     public int CustomerId;     public string FirstName;     public string LastName;     public string EmailAddress;  static private int nextCustId = 1;  public Customer(string first, string last, string email)     {        CustomerId = nextCustId++;        FirstName = first;        LastName = last;        EmailAddress = email;     }  public static int GetNextId()   {   return nextCustId;   }  } 

Here is the test program:

 // TestCustomer.cs - Step 2  using System;  public class TestCustomer  {     public static void Main()     {        Console.WriteLine("next id = {0}",                          Customer.GetNextId());        Customer cust1, cust2;        cust1 = new Customer("John", "Doe",                             "john@rocky.com");        cust2 = new Customer("Mary", "Smith",                             "mary@moose.edu");        ShowCustomer("cust1", cust1);        ShowCustomer("cust2", cust2);     }     private static void ShowCustomer(string label,                                      Customer cust)     ... 

Note that the static method GetNextId is accessed through the class Customer and not through an object reference such as cust1 . This program also illustrates the fact that Main is a static method and is invoked by the runtime without an instance of the TestCustomer class being created. Since there is no instance, any method of TestCustomer called from within Main must also be declared static , as illustrated by ShowCustomer .

Static Constructor

Besides having static fields and static methods, a class may also have a static constructor . A static constructor is called only once, before any object instances have been created. A static constructor is defined by prefixing the constructor with static . A static constructor mut take no parameters and has no access modifier (such as public or private ).

In a language such as C++, where there can be global variables not attached to any class, you may initialize a library through the constructor for a global object. In C# there are no such freestanding global objects, but you can achieve similar initialization through use of a static constructor. As a somewhat whimsical example of a static constructor, consider the StaticWorld program, which provides an alternative implementation of "Hello, World."

 // StaticWorld.cs  public class Hello  {     static Hello()     {        System.Console.Write("Hello, ");     }     public static void World()     {        System.Console.WriteLine("World");     }  }  public class World  {     public static void Main(string[] args)     {         Hello.World();     }  } 

Constant and Readonly Fields

If you want to make sure that a variable always has the same value, you can assign the value via an initializer and use the const modifier. Such a constant is automatically static, and you will access it from outside the class through the class name.

Another situation may call for a one-time initialization at runtime, and after that the value cannot be changed. You can achieve this effect through a readonly field. Such a field may be either an instance member or a static member. In the case of an instance member, it will be assigned in an ordinary constructor. In the case of a static member, it will be assigned in a static constructor.

The program ConstantHotel illustrates the use of both const and readonly . In both cases, you will get a compiler error if you try to modify the value.

 // ConstantHotel.cs  public class Hotel  {  public const decimal rate = 100m;   public readonly string name;  public Hotel(string name)     {        this.name = name;     }  } 

Here is the test program:

 // TestHotel.cs  using System;  public class TestHotel  {     public static void Main()     {        Console.WriteLine("rate = {0:C}", Hotel.rate);  //Hotel.rate = 150m;          // illegal  Hotel hotel = new Hotel("Ritz");        Console.WriteLine("hotel name = {0}", hotel.name);  //hotel.name = "Sheraton";    // illegal  }  } 

Here is the output:

 rate = 0.00  hotel name = Ritz 
for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net