Previous Section  < Day Day Up >  Next Section

3.4. Constants, Fields, and Properties

Constants, fields, and properties are the members of a class that maintain the content or state of the class. As a rule of thumb, use constants for values that will never change; use fields to maintain private data within a class; and use properties to control access to data in a class. Let's now look at the details of these three class members.

Constants

C# uses the const keyword to declare variables that have a fixed, unalterable value. Listing 3-1 provides an example of using constants. Although simple, the code illustrates several basic rules for defining and accessing constants.

Listing 3-1. Constants

using System;

class Conversions

{

   public const double Cm = 2.54;

   public const double Grams = 454.0 , km = .62 ;

   public const string ProjectName = "Metrics";

}

class ShowConversions

{

   static void Main()

   {

      double pounds, gramWeight;

      gramWeight = 1362;

      pounds = gramWeight / Conversions.Grams;

      Console.WriteLine(

         "{0} Grams= {1} Pounds", gramWeight,pounds);

      Console.WriteLine("Cm per inch {0}", Conversions.Cm);

      Conversions c= new Conversions();  // Create class

                                         // instance

      // This fails to compile. Cannot access const from object

      Console.WriteLine("Cm per inch {0}", c.Cm);

   }

}


  • The const keyword can be used to declare multiple constants in one statement.

  • A constant must be defined as a primitive type, such as string or double shown here (see Chapter 2 for a list of all C# primitives).

  • Constants cannot be accessed from an instance of the class. Note that the ShowConversion class accesses the constants without instantiating the class.

The most important thing to recognize about a constant is that its value is determined at compile time. This can have important ramifications. For example, suppose the Furniture class in Figure 3-1 is contained in a DLL that is used by other assemblies as a source for the sales tax rate. If the rate changes, it would seem logical that assigning the new value to SalesTax and recompiling the DLL would then make the new value to external assemblies. However, the way .NET handles constants requires that all assemblies accessing the DLL must also be recompiled. The problem is that const types are evaluated at compile time.

When any calling routine is compiled against the DLL, the compiler locates all constant values in the DLL's metadata and hardcodes them in the executable code of the calling routine. This value is not changed until the calling routine is recompiled and reloads the value from the DLL. In cases such as tax, where a value is subject to change, it's preferable to define the value as a readonly field梥ometimes referred to as a runtime constant.

Core Approach

It is often desirable to store a group of constants in a special utility or helper class. Declare them public to make them available to all classes. Because there is no need to create instances of the class, declare the class as abstract.


Fields

A field is also used to store data within a class. It differs from a const in two significant ways: Its value is determined at runtime, and its type is not restricted to primitives.

Field Modifiers

In addition to the access modifiers, fields have two additional modifiers: static and readonly (see Table 3-3).

Table 3-3. Field Modifiers

Modifier

Definition

static

The field is part of the class's state rather than any instances of the class. This means that it can be referenced directly (like a constant) by specifying classname.fieldname without creating an instance of the class.

readonly

The field can only be assigned a value in the declaration statement or class constructor. The net effect is to turn the field into a constant. An error results if code later attempts to change the value of the field.


As a rule of thumb, fields should be defined with the private attribute to ensure that the state of an object is safe from outside manipulation. Methods and properties should then be used to retrieve and set the private data if outside access is required.

Core Note

If a field is not initialized, it is set to the default value for its type: 0 for numbers, null for a reference type, single quotation marks ('') for a string, and false for boolean.


There is one case where setting a field to public makes sense: when your program requires a global constant value. By declaring a field to be public static readonly, you can create a runtime constant. For example, this declaration in Figure 3-1:


const double salesTax = .065;


can be replaced with a field


public static readonly double SalesTax = .065;


A method then references this field as Furniture.SalesTax, and the readonly modifier ensures the value cannot be changed. Note that if you create an instance of this class, you cannot access salesTax as a member of that instance. An attempt to do so results in the compile error "static member cannot be accessed with an instance reference".


Furniture chair = new Furniture("Broyhill","12422",225.00);

double itemTax = chair.SalesTax;    // Raises an error


Using Static Read-Only Fields to Reference Class Instances

Static readonly fields can be used to represent groups of related constant data by declaring them a reference type such as a class instance or array. This is of use when the data can be represented by a limited number of objects with unchanging values.

This example presents the interesting concept of fields defined as instances of their containing class. The static modifier makes this possible, because it designates the field as part of the class and not its instances. Note that the private constructor prevents clients outside the scope of the class from creating new class instances. Thus, only those objects exposed by the fields are available outside the class.

The class also contains two instance fields, yardPrice and deliveryWeeks, that are declared as private. Access to them is controlled though a public method and property:


Upholstery.silk.FabCost(10);   // Value from method

Upholstery.silk.DeliveryTime;  // Value from property


Listing 3-2. Using Static Read-Only Fields as Reference Types

public class Upholstery

{

   // fields to contain price and delivery time for fabrics

   public static readonly Upholstery silk =

      new Upholstery(15.00, 8);

   public static readonly Upholstery wool =

      new Upholstery(12.00, 6);

   public static readonly Upholstery cotton =

      new Upholstery(9.00, 6);

   private double yardPrice;

   private int deliveryWeeks;

   // constructor - set price per yard and delivery time

   // private modifier prevents external class instantiation

   private Upholstery ( double yrPrice, int delWeeks)

   {

      yardPrice = yrPrice;

      deliveryWeeks = delWeeks;

   }

   // method to return total cost of fabric

   public double FabCost(double yards)

   {

      return yards * this.yardPrice;

   }

   // property to return delivery time

   public int DeliveryTime

      {get { return deliveryWeeks;}}

   // property to return price per yard

   public double PricePerYard

      {get {return yardPrice;}}

}


Properties

A property is used to control read and write access to values within a class. Java and C++ programmers create properties by writing an accessor method to retrieve field data and a mutator method to set it. Unlike these languages, the C# compiler actually recognizes a special property construct and provides a simplified syntax for creating and accessing data. In truth, the syntax is not a whole lot different than a comparable C++ implementation, but it does allow the compiler to generate more efficient code.

Syntax:


[attributes] <modifier>   <data type>  <property name>

{

   [access modifier] get

   {  ...

      return(propertyvalue)

   }

   [access modifier] set

   {  ...  Code to set a field to the keyword value  }

}


Note:

  1. In addition to the four access modifiers, the property modifier may be static, abstract, new, virtual, or override. Abstract is used only in an abstract class; virtual is used in a base class and permits a subclass to override the property.

  2. value is an implicit parameter containing the value passed when a property is called.

  3. The get and set accessors may have different access modifiers.

Listing 3-3. Creating and Accessing a Property

public class  Upholstery

{

   private double yardPrice;

   // Property to return or set the price

   public double PricePerYard

   {

      get {return yardPrice;}   // Returns a property value

      set {                     // Sets a property value

         if ( value <= 0 )

            throw new ArgumentOutOfRangeException(

            "Price must be greater than 0.");

         yardPrice = value

      }

   }

   ...

}


The syntax for accessing the property of a class instance is the same as for a field:


// fabObj is instance of Upholstery class

double fabricPrice = fabObj.PricePerYard;

fabObj.PricePerYard = 12.50D;


The get block of code serves as a traditional accessor method and the set block as a mutator method. Only one is required. Leave out the get block to make the property write-only or the set block to make it read-only.

All return statements in the body of a get block must specify an expression that is implicitly convertible to the property type.

In this example, the code in the set block checks to ensure that the property is set to a value greater than 0. This capability to check for invalid data is a major argument in favor of encapsulating data in a property.

If you were to examine the underlying code generated for this example, you would find that C# actually creates a method for each get or set block. These names are created by adding the prefix get or set to the property name梖or example, get_PricePerYard. In the unlikely case you attempt to create a method with the same name as the internal one, you will receive a compile-time error.

The use of properties is not necessarily any less efficient than exposing fields directly. For a non-virtual property that contains only a small amount of code, the JIT (Just-in-Time) compiler may replace calls to the accessor methods with the actual code contained in the get or set block. This process, known as inlining, reduces the overhead of making calls at runtime. The result is code that is as efficient as that for fields, but much more flexible.

Indexers

An indexer is often referred to as a parameterized property. Like a property, it is declared within a class, and its body may contain get and set accessors that share the same syntax as property accessors. However, an indexer differs from a property in two significant ways: It accepts one or more parameters, and the keyword this is used as its name. Here is the formal syntax:

Syntax:


[attributes] <modifier><return type> this[parameter(s)]  {


Example:


public int this [int ndx] {


Note: The static modifier is not supported because indexers work only with instances.

In a nutshell, the indexer provides a way to access a collection of values maintained within a single class instance. The parameters passed to the indexer are used as a single- or multi-dimensional index to the collection. The example in Listing 3-4 should clarify the concept.

Listing 3-4. Using Indexer to Expose an Array of Objects

using System;

using System.Collections;  // Namespace containing ArrayList

public class  Upholstery

{

   // Class to represent upholstery fabric

   private double yardPrice;

   private int deliveryWeeks;

   private string fabName;

   // Constructor

   public Upholstery (double price, int delivery, string fabric)

   {

      this.yardPrice = price;

      this.deliveryWeeks = delivery;

      this.fabName = fabric;

   }

   // Three readonly properties to return Fabric information

   public int DeliveryTime

      {get {return deliveryWeeks;}}

   public double PricePerYard

      {get {return yardPrice;}}

   public string FabricName

      {get {return fabName;}}

}

public class Fabrics

{

   // Array to hold list of objects

   private ArrayList fabricArray = new ArrayList();

   // Indexer to add or return an object from the array

   public Upholstery this[int ndx]

   {

      get {

           if(!(ndx<0 || ndx > fabricArray.Count-1))

              return (Upholstery)fabricArray[ndx];

           // Return empty object

           else return(new Upholstery(0,0,""));

          }

           set { fabricArray.Insert(ndx, value);}

   }

}

public class IndexerApp

{

   public static void Main()

   {

      Fabrics sofaFabric = new Fabrics();

      // Use Indexer to create array of Objects

      sofaFabric[0] = new Upholstery(15.00, 8, "Silk");

      sofaFabric[1] = new Upholstery(12.00, 6, "Wool");

      sofaFabric[2] = new Upholstery(9.00, 6, "Cotton");

      // Next statement prints "Fabric: Silk"

      Console.WriteLine("Fabric: {0} ",

                        sofaFabric[0].FabricName);

   }

}


The Fabrics class contains an indexer that uses the get and set accessors to control access to an internal array of Upholstery objects. A single instance of the Fabrics class is created and assigned to sofaFabric. The indexer allows the internal array to be directly accessed by an index parameter passed to the object:


Fabrics sofaFabric = new Fabrics();

// Use Indexer to create array of Objects

sofaFabric[0] = new Upholstery(15.00, 8, "Silk");

sofaFabric[1] = new Upholstery(12.00, 6, "Wool");


The advantage of using an indexer is that it hides the array handling details from the client, and it can also perform any validation checking or data modification before returning a value. Indexers are best used with objects whose properties can be represented as a collection, rather than a scalar value. In this example, the various fabrics available for sofas form a collection. We could also use the indexer to create a collection of fabrics used for curtains:


Fabrics curtainFabric = new Fabrics();

curtainFabric[0] = new Upholstery(11.00, 4, "Cotton");

curtainFabric[1] = new Upholstery(7.00, 5, "Rayon");


    Previous Section  < Day Day Up >  Next Section