Chapter 3. Class Design in C#

Topics in This Chapter

  • Defining a ClassThe attributes and modifiers included in a class definition influence the behavior and accessibility of the class.

  • ConstantsA const type defines fixed values at compilation time.

  • FieldsA field is typically used to maintain data inside a class.

  • PropertiesA property is the recommended way to expose a class's data.

  • MethodsThe functionality of a class is defined by its methods.

  • InheritanceBy using inheritance, a class can take advantage of preexisting types.

  • ConstructorsThis special purpose method is used to initialize a class.

  • Events and DelegatesA program's actions are often triggered by events; delegates have the role of invoking methods to handle an event.

  • Operator OverloadingOperators can be used to manipulate classes.

  • InterfacesAn inherited interface defines the methods and properties that a struct or class must implement.

  • GenericsBy creating a generic class to store data, it is possible to eliminate casting, boxing, and ensure type safety.

  • StructuresIn some situations, a struct is a better choice than a class.

This chapter provides an advanced introduction to using classes within the .NET environment. It is not a primer on object-oriented programming (OOP) and assumes you have some familiarity with the principles of encapsulation, inheritance, and polymorphism. C# is rich in object-oriented features, and the first challenge in working with classes is to understand the variety of syntactical contstructs. At the same time, it is necessary to appreciate the interaction between C# and .NET. Not only does the Framework Class Library (FCL) provide thousands of predefined classes, but it also provides a hierarchy of base classes from which all C# classes are derived.

The chapter presents topics in a progressive fashion—each section building on the previous. If you are new to C#, you should read from beginning to end; if you're familiar with the concepts and syntax, read sections selectively. The chapter begins by presenting the syntactical construct of a class and then breaks it down in detail. Attributes, modifiers, and members of the class body (constructors, properties, fields, and methods) are all explained. Sprinkled throughout are recommended .NET guidelines and best practices for designing and using custom classes. The objective is to show not only how to use classes, but also encourage good design practices that result in efficient code.

Introduction to a C# Class

Figure 3-1 displays a class declaration followed by a body containing typical class members: a constant, fields, a constructor containing initialization code, a property, and a method. Its purpose is to familiarize you with the syntax common to most C# classes and serve as a reference for later examples.

Class declaration and body

Figure 3-1. Class declaration and body

Defining a Class

A class definition consists of an optional attributes list, optional modifiers, the word class followed by the class identifier (name), and an optional list containing a base class or interfaces to be used for inheritance. Following this class declaration is the class body, consisting of the code and class members such as methods and properties.

Syntax for class definition:

[attributes] [modifiers] class  identifier [:baselist]
{class body} [;]

Classes—as do all .NET types—inherit from the System.Object class. This inheritance is implicit and is thus not specified as part of the class definition. As we'll see in the discussion of inheritance, this is important because a class can explicitly inherit from only one class.

Attributes

The optional attribute section consists of a pair of square brackets surrounding a comma-separated list of one or more attributes. An attribute consists of the attribute name followed by an optional list of positional or named arguments. The attribute may also contain an attribute target—that is, the entity to which the attribute applies.

Examples

The attribute section contains an attribute name only:

[ClassDesc]

Single attribute with named argument and positional argument (0):

[ClassDesc(Author="Knuth", 0)]

Multiple attributes can be defined within brackets.:

[ClassDesc(Author="Knuth"), ClassDesc(Author="James")]

Description

Attributes provide a way to associate additional information with a target entity. In our discussion, the target is a newly created class; but attributes may also be associated with methods, fields, properties, parameters, structures, assemblies, and modules. Their simple definition belies a truly innovative and powerful programming tool. Consider the following:

  • An attribute is an instance of a public class. As such, it has fields and properties that can be used to provide rich descriptive information about a target or targets.

  • All compilers that target the Common Language Runtime (CLR) recognize attributes and store information about them in the module's metadata. This is an elegant way to attach information to a program entity that can affect its behavior without modifying its implementation code. An application can then use reflection (a set of types for reading metadata) to read the metadata at runtime and make decisions based on its value.

  • Hundreds of predefined attributes are included in the .NET Framework Class Library (FCL). They are used heavily in dealing with interoperability issues such as accessing the Win32API or allowing .NET applications and COM objects to communicate. They also are used to control compiler operations. The[assembly:CLSComplianttrue)] attribute in Figure 3-1 tells the C# compiler to check the code for CLS compliance.

Core Note

Core Note

Attributes provide a way to extend the metadata generated by the C# compiler with custom descriptive information about a class or class member.

.NET supports two types of attributes: custom attributes and standard attributes. Custom attributes are defined by the programmer. The compiler adds them to the metadata, but it's up to the programmer to write the reflection code that incorporates this metadata into the program. Standard attributes are part of the .NET Framework and recognized by the runtime and .NET compilers. The Flags attribute that was discussed in conjunction with enums in Chapter 2, “C# Language Fundamentals,” is an example of this; another is the conditional attribute, described next.

Conditional Attribute

The conditional attribute is attached to methods only. Its purpose is to indicate whether the compiler should generate Intermediate Language (IL) code to call the method. The compiler makes this determination by evaluating the symbol that is part of the attribute. If the symbol is defined (using the define preprocessor directive), code that contains calls to the method is included in the IL. Here is an example to demonstrate this:

File: attribs.cs (attribs.dll)

#define DEBUG
using System;
using System.Diagnostics;   // Required for conditional attrib.
public class AttributeTest
{
   [Conditional("TRACE")]
   public static void ListTrace()
   { Console.WriteLine("Trace is On"); }
   [Conditional("DEBUG")]
   public static void ListDebug()
   { Console.WriteLine("Debug is On"); }
}

File: attribclient.cs (attribclient.exe)

#define TRACE
using System;
public class MyApp {
   static void Main()
   {
      Console.WriteLine("Testing Method Calls");
      AttributeTest.ListTrace();
      AttributeTest.ListDebug();
   }
}

Executing attribclient yields the following output:

Testing Method Calls
Trace is On

When attribclient is compiled, the compiler detects the existence of the TRACE symbol, so the call to ListTrace is included. Because DEBUG is not defined, the call to ListDebug is excluded. The compiler ignores the fact that DEBUG is defined in attribs; its action is based on the symbols defined in the file containing the method calls. Note that a conditional attribute can be used only with methods having a return type of void.

Access Modifiers

The primary role of modifiers is to designate the accessibility (also called scope or visibility) of types and type members. Specifically, a class access modifier indicates whether a class is accessible from other assemblies, the same assembly, a containing class, or classes derived from a containing class.

public

A class can be accessed from any assembly.

protected

Applies only to a nested class (class defined within another class). Access is limited to the container class or classes derived from the container class.

internal

Access is limited to classes in the same assembly. This is the default access.

private

Applies only to a nested class. Access is limited to the container class.

protected internal

The only case where multiple modifiers may be used.

internal

Access is limited to the current assembly or types derived from the containing class.

Core Note

Core Note

A base class must be at least as accessible as its derived class. The following raises an error:

class  Furniture { }               // default access is internal
public class Sofa : Furniture { }  // error

The error occurs because the Furniture class (internal by default) is less accessible than the derived Sofa class. Errors such as this occur most frequently when a developer relies on a default modifer. This is one reason that modifiers should be included in a declaration.

Abstract, Sealed, and Static Modifiers

In addition to the access modifiers, C# provides a dozen or so other modifiers for use with types and type members. Of these, three can be used with classes: abstract, sealed, and static.

abstract

Indicates that a class is to be used only as a base class for other classes. This means that you cannot create an instance of the class directly. Any class derived from it must implement all of its abstract methods and accessors. Despite its name, an abstract class can possess nonabstract methods and properties.

sealed

Specifies that a class cannot be inherited (used as a base class). Note that .NET does not permit a class to be both abstract and sealed.

static

Specifies that a class contains only static members (.NET 2.0).

Class Identifier

This is the name assigned to the class. The ECMA standard recommends the following guidelines for naming the identifier:

  • Use a noun or noun phrase.

  • Use the Pascal case capitalization style: The first letter in the name and the first letter of each subsequent concatenated word are capitalized—for example, BinaryTree.

  • Use abbreviations sparingly.

  • Do not use a type prefix, such as C, to designate all classes—for example, BinaryTree, not CBinaryTree.

  • Do not use the underscore character.

  • By convention, interface names always begin with I; therefore, do not use I as the first character of a class name unless I is the first letter in an entire word—for example, IntegralCalculator.

Base Classes, Interfaces, and Inheritance

This optional list contains a previously defined class or interface(s) from which a class may derive its behavior and capabilities. The new class is referred to as the derived class, and the class or interface from which it inherits is the base class or interface. A base class must be listed before any interface(s).

Example

// .. FCL Interface and user-defined base class
public interface System.Icomparable
   {Int32 CompareTo(Object object); }
class Furniture {  }
// .. Derived Classes
class Sofa: Furniture { ... }  // Inherits from one base class
// Following inherits from one base class and one interface.
class Recliner: Furniture, IComparable {...}

The C# language does not permit multiple class inheritance, thus the base list can contain only one class. Because there is no limit on the number of inherited interfaces, this serves to increase the role of interfaces in the .NET world.

Core Note

Core Note
  • Inheritance from a base class is referred to as implementation inheritance. The derived class inherits all of the members of the base class. However, the base class can prevent access to a member by defining it with the private modifier.

  • Inheritance from an interface is referred to as interface inheritance because the interface does not provide implementation code. The derived class must provide the logic to implement any functions defined in the base interface(s).

Overview of Class Members

Table 3-1 provides a summary of the types that comprise a .NET class. They can be classified broadly as members that hold data—constants, fields, and properties—and members that provide functionality—the constructor, method, and event. We'll look at each individually.

Table 3-1. Class Members

Member Type

Valid In

Description

Constant

Class, Structure

A symbol that represents an unchanging value. The compiler associates it with the class—not an instance of the class.

Field

Class, Structure

A variable that holds a data value. It may be read-only or read/write.

Property

Class, Structure

Provides access to a value in a class. It uses an accessor that specifies the code to be executed in order to read or write the value. The code to read or write to a property is implemented implicitly by .NET as two separate methods.

Constructor

Class, Structure

C# has three types of constructors:

  • InstanceInitializes fields when an instance of a class is created.

  • PrivateCommonly used to prevent instances of a class from being created.

  • StaticInitializes class before any instance is created.

Method

Class, Structure, Interface

A function associated with the class that defines an action or computation.

Events

Class, Structure, Interface

A way for a class or object to notify other classes or objects that its state has changed.

Types

Class, Structure, Interface

Classes, interfaces, structs, delegates.

Member Access Modifiers

The access modifiers used for a class declaration can also be applied to class members. They determine the classes and assemblies that have access to the class. Table 3-2 summarizes the scope of accessibility.

Table 3-2. Summary of Accessibility Provided by Access Modifiers

 

Access Modifiers

Class can be accessed by classes in:

Public

protected

Internal

private

Another assembly

Yes

[*]

No

[*]

Same assembly

Yes

[*]

Yes

[*]

Containing class

Yes

Yes

Yes

Yes

Class derived from containing class

Yes

Yes

Yes

No

[*] Not applicable

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.

Example 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—sometimes referred to as a runtime constant.

Core Approach

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

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

Example 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.

Example 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—for 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] {

NoteThe 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.

Example 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");

Methods

Methods are to classes as verbs are to sentences. They perform the actions that define the behavior of the class. A method is identified by its signature, which consists of the method name and the number and data type of each parameter. A signature is considered unique as long as no other method has the same name and matching parameter list. In addition to parameters, a method has a return type—void if nothing is returned—and a modifier list that determines its accessibility and polymorphic behavior.

For those who haven't recently boned up on Greek or object-oriented principles, polymorphism comes from the Greek poly (many) and morphos (shape). In programming terms, it refers to classes that share the same methods but implement them differently. Consider the ToString method implemented by all types. When used with an int type, it displays a numeric value as text; yet on a class instance, it displays the name of the underlying class—although this default should be overridden by a more meaningful implementation.

One of the challenges in using .NET methods is to understand the role that method modifiers play in defining the polymorphic behavior of an application. A base class uses them to signal that a method may be overridden or that an inheriting class must implement it. The inheriting class, in turn, uses modifiers to indicate whether it is overriding or hiding an inherited method. Let's look at how all this fits together.

Method Modifiers

In addition to the access modifiers, methods have seven additional modifiers shown in Table 3-4. Five of these—new, virtual, override, sealed, and abstract—provide a means for supporting polymorphism.

Table 3-4. Method Modifiers

Modifier

Description

static

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

virtual

Designates that the method can be overridden in a subclass. This cannot be used with static or private access modifiers.

override

Specifies that the method overrides a method of the same name in a base class. This enables the method to define behavior unique to the subclass. The overriden method in the base class must be virtual.

new

Permits a method in an inherited class to “hide” a non-virtual method with a same name in the base class. It replaces the original method rather than overriding it.

sealed

Prevents a derived class from overriding this method.

  • Is used in a derived class that will serve as the base for its own subclasses.

  • Must be used with the override modifier.

abstract

The method contains no implementation details and must be implemented by any subclass. Can only be used as a member of an abstract class.

extern

Indicates that the method is implemented externally. It is generally used with the DLLImport attribute that specifies a DLL to provide the implementation.

Static Modifier

As with other class members, the static modifier defines a member whose behavior is global to the class and not specific to an instance of a class. The modifier is most commonly used with constructors (described in the next section) and methods in helper classes that can be used without instantiation.

Example 3-5. Static Method

using System;
class Conversions
{
   // class contains functions to provide metric conversions
   private static double cmPerInch = 2.54;
   private static double gmPerPound = 455;
   public static double inchesToMetric(double inches) {
      return(inches * cmPerInch);
   }
   public static double poundsToGrams(double pounds) {
      return(pounds * gmPerPound);
   }
}
class Test
{
   static void Main() {
      double cm, grams;
      cm = Conversions.inchesToMetric(28.5);
      grams = Conversions.poundsToGrams(984.4);
   }
}

In this example, the Conversions class contains methods that convert units from the English to metric system. There is no real reason to create an instance of the class, because the methods are invariant (the formulas never change) and can be conveniently accessed using the syntax classname.method(parameter).

Method Inheritance with Virtual and Override Modifiers

Inheritance enables a program to create a new class that takes the form and functionality of an existing (base) class. The new class then adds code to distinguish its behavior from that of its base class. The capability of the subclass and base class to respond differently to the same message is classical polymorphism. In practical terms, this most often means that a base and derived class(es) provide different code for methods having the same signature.

By default, methods in the base class cannot be changed in the derived class. To overcome this, .NET provides the virtual modifier as a cue to the compiler that a method can be redefined in any class that inherits it. Similarly, the compiler requires that any derived class that alters a virtual method preface the method with the override modifier. Figure 3-2 and Listing 3-6 provide a simple illustration of this.

Example 3-6. Virtual Methods

using System;
class Fiber
{
   public  virtual string ShowMe() { return("Base");}
}
class Natural:Fiber
{
   public override string ShowMe() { return("Natural");}
}
class Cotton:Natural
{
   public override string ShowMe() { return("Cotton");}
}
class Test
{
   static void Main ()
   {
      Fiber fib1 = new Natural();  // Instance of Natural
      Fiber fib2 = new Cotton();   // Instance of Cotton
      string fibVal;
      fibVal = fib1.ShowMe();      // Returns "Natural"
      fibVal = fib2.ShowMe();      // Returns "Cotton"
   }
}
Relationship between base class and subclasses for Listing 3-6

Figure 3-2. Relationship between base class and subclasses for Listing 3-6

In this example, Cotton is a subclass of Natural, which is itself a subclass of Fiber. Each subclass implements its own overriding code for the virtual method ShowMe.

fib1.ShowMe();    //returns  "Natural"
fib2.ShowMe();    //returns  "Cotton"

A subclass can inherit a virtual method without overriding it. If the Cotton class does not override ShowMe(), it uses the method defined in its base class Natural. In that case, the call to fib2.ShowMe() would return “Natural”.

New Modifier and Versioning

There are situations where it is useful to hide inherited members of a base class. For example, your class may contain a method with the same signature as one in its base class. To notify the compiler that your subclass is creating its own version of the method, it should use the new modifier in the declaration.

ShowMe() is no longer virtual and cannot be overridden. For Natural to create its own version, it must use the new modifier in the method declaration to hide the inherited version. Observe the following:

  • An instance of the Natural class that calls ShowMe()invokes the new method:

    Natural myFiber = new Natural();
    string fibTtype = myFiber.ShowMe();  // returns "Natural"
    
  • Cotton inherits the new method from Natural:

    Cotton myFiber = new Cotton();
    string fibType = myFiber.ShowMe();   // returns "Natural"
    
  • If ShowMe were declared as private rather than public in Natural, Cotton would inherit ShowMe from Fiber, because it cannot inherit a method that is out of scope.

Example 3-7. Using the New Modifier for Versioning

public class Fiber
{
   public string ShowMe()        {return("Base");}
   public virtual string GetID() {return("BaseID");}
}
public class Natural:Fiber
{
   // Hide Inherited version of ShowMe
   new public string ShowMe()     {return("Natural");}
   public override string GetID() {return("NaturalID");}
   }
public class Cotton:Natural
{  // Inherits two methods: ShowMe() and GetID()
}

Sealed and Abstract Modifiers

A sealed modifier indicates that a method cannot be overridden in an inheriting class; an abstract modifier requires that the inheriting class implement it. In the latter case, the base class provides the method declaration but no implementation.

This code sample illustrates how an inheriting class uses the sealed modifier to prevent a method from being overridden further down the inheritance chain. Note that sealed is always paired with the override modifier.

class A {
   public virtual void PrintID{....}
}
class B: A {
   sealed override public void PrintID{...}
}
class C:B {
   // This is illegal because it is sealed in B.
   override public void PrintID{...}
}

An abstract method represents a function with a signature—but no implementation code—that must be defined by any non-abstract class inheriting it. This differs from a virtual method, which has implementation code, but may be redefined by an inheriting class. The following rules govern the use abstract methods:

  • Abstract methods can only be declared in abstract classes; however, abstract classes may have non-abstract methods.

  • The method body consists of a semicolon:

    public abstract void myMethod();
    
  • Although implicitly virtual, abstract methods cannot have the virtual modifier.

  • A virtual method can be overridden by an abstract method.

When facing the decision of whether to create an abstract class, a developer should also consider using an interface. The two are similar in that both create a blueprint for methods and properties without providing the implementation details. There are differences between the two, and a developer needs to be aware of these in order to make the better choice. The section on interfaces in this chapter offers a critical comparison.

Passing Parameters

By default, method parameters are passed by value, which means that a copy of the parameter's data—rather than the actual data—is passed to the method. Consequently, any change the target method makes to these copies does not affect the original parameters in the calling routine. If the parameter is a reference type, such as an instance of a class, a reference to the object is passed. This enables a called method to change or set a parameter value.

C# provides two modifiers that signify a parameter is being passed by reference: out and ref. Both of these keywords cause the address of the parameter to be passed to the target method. The one you use depends on whether the parameter is initialized by the calling or called method. Use ref when the calling method initializes the parameter value, and out when the called method assigns the initial value. By requiring these keywords, C# improves code readability by forcing the programmer to explicitly identify whether the called method is to modify or initialize the parameter.

The code in Listing 3-8 demonstrates the use of these modifiers.

Example 3-8. Using the ref and out Parameter Modifiers

class TestParms
{
   public static void FillArray(out double[] prices)
   {
      prices = new double[4] {50.00,80.00,120.00,200.00};
   }
   public static void UpdateArray(ref double[] prices)
   {
      prices[0] = prices[0] * 1.50;
      prices[1] = prices[1] * 2.0;
   }
   public static double TaxVal(double ourPrice,
                               out double taxAmt)
   {
      double totVal = 1.10 * ourPrice;
      taxAmt = totVal – ourPrice;
      ourPrice = 0.0;  // Does not affect calling parameter
      return totVal;
   }
}
class MyApp
{
   public static void Main()
   {
      double[] priceArray;
      double taxAmt;
      //   (1) Call method to initialize array
      TestParms.FillArray(out priceArray);
      Console.WriteLine( priceArray[1].ToString());  // 80
      //   (2) Call method to update array
      TestParms.UpdateArray(ref priceArray);
      Console.WriteLine( priceArray[1].ToString());  // 160
      //   (3) Call method to calculate amount of tax.
      double ourPrice = 150.00;
      double newtax = TestParms.TaxVal(ourPrice, out taxAmt);
      Console.WriteLine( taxAmt.ToString());  // 15
      Console.WriteLine( ourPrice);           // 150.00
   }
}

In this example, the class MyApp is used to invoke three methods in the TestParms class:

  1. FillArray is invoked to initialize the array. This requires passing the array as a parameter with the out modifier.

  2. The returned array is now passed to a second method that modifies two elements in the array. The ref modifier indicates the array can be modified.

  3. ourPrice is passed to a method that calculates the amount of tax and assigns it to the parameter taxAmt. Although ourPrice is set to 0 within the method, its value remains unchanged in the calling method because it is passed by value.

C# includes one other parameter modifier, params, which is used to pass a variable number of arguments to a method. Basically, the compiler maps the variable number of arguments in the method invocation into a single parameter in the target method. To illustrate, let's consider a method that calculates the average for a list of numbers passed to it:

// Calculate average of variable number of arguments
public static double GetAvg(params double[] list)
{
   double tot = 0.0;
   for (int i = 0 ; i < list.Length; i++)
      tot += list[i];
   return tot / list.Length;
}

Except for the params modifier, the code for this method is no different than that used to receive an array. Rather than sending an array, however, the invoking code passes an actual list of arguments:

double avg;
avg = TestParms.GetAvg(12,15, 22, 5, 7 ,19);
avg = TestParms.GetAvg(100.50, 200, 300, 55,88,99,45);

When the compiler sees these method calls, it emits code that creates an array, populates it with the arguments, and passes it to the method. The params modifier is essentially a syntactical shortcut to avoid the explicit process of setting up and passing an array.

Core Note

Core Note

The params keyword can only be used with the last parameter to a method.

Constructors

The Common Language Runtime (CLR) requires that every class have a constructor—a special-purpose method that initializes a class or class instance the first time it is referenced. There are three basic types of constructors: instance, private (a special case of instance), and static.

Instance Constructor

Syntax

[attributes] [modifiers]  <identifier> ( [parameter-list])
[:initializer] { constructor-body}

The syntax is the same as that of the method except that it does not have a return data type and adds an initializer option. There are a number of implementation and behavioral differences:

  • The identifier (constructor name) must be the same as the class name.

  • The initializer provides a way to invoke code prior to entering the constructor-body. It takes two forms:

    base(argument list)—. Calls a base class

    this(argument list)—. Calls another constructor in the same class

  • If no explicit constructor is provided for a class, the compiler creates a default parameterless constructor.

The instance constructor is called as part of creating a class instance. Because a class may contain multiple constructors, the compiler calls the constructor whose signature matches that of the call:

Fiber fib1 = new Cotton();
Fiber fib1 = new Cotton("Egyptian");

.NET constructs an object by allocating memory, zeroing out the memory, and calling the instance constructor. The constructor sets the state of the object. Any fields in the class that are not explicitly initialized are set to zero or null, depending on the associated member type.

Inheritance and Constructors

Constructors, unlike methods, are not inherited. It is up to each class to define its own constructor(s) or use the default parameterless constructor. Each constructor must call a constructor in its base class before the first line of the calling constructor is executed. Because C# generates a call to the base class's default constructor automatically, the programmer typically does not bother with this. But there are exceptions. Consider this code in which the base class Apparel defines a constructor:

public class Apparel
{
   private string color;
   private decimal price;
   // constructor
   public Apparel(string c, decimal p, string b)
   {
      color = c;
      price = p;
   }
}
// class inheriting from Apparel
class Coat: Apparel
{
   private string length;
   public Coat(string c, decimal p, string b, string l)
   {
      length = l;
      // ... other code
   }
}

If you try to compile this, you'll get an error stating that “no overload for Apparel takes 0 arguments”. Two factors conspire to cause this: first, because Apparel has an explicit constructor, the compiler does not add a default parameterless constructor to its class definition; and second, as part of compiling the constructor in the derived class, the compiler includes a call to the base class's default constructor—which in this case does not exist. The solution is either to add a parameterless constructor to Apparel or to include a call in the derived class to the explicit constructor. Let's look at how a constructor can explicitly call a constructor in a base class.

Using Initializers

The C# compiler provides the base initializer as a way for a constructor in an inherited class to invoke a constructor in its base class. This can be useful when several classes share common properties that are set by the base class constructor. Listing 3-9 demonstrates how a derived class, Shirt, uses a base initializer to call a constructor in Apparel.

Example 3-9. A Constructor with a Base Initializer

using System;
public  class Apparel
{
   private string color;
   private decimal price;
   private string brand;
   // Constructor
   public Apparel(string c,decimal p, string b)
   {
      color = c;
      price = p;
      brand = b;
   }

   public string ItemColor
   {
      get {return color;}
   }
   //  other properties and members go here
}

public class Shirt: Apparel
{
   private decimal mySleeve;
   private decimal myCollar;
   public Shirt (string c, decimal p, string b,
      decimal sleeve, decimal collar)  : base(c,p,b)
   {
      mySleeve = sleeve;
      myCollar = collar;
   }
}
public class TestClass
{
   static void Main()
   {
      Shirt shirtClass = new Shirt("white", 15.00m, "Arrow",
                                   32.0m, 15.5m);
      Console.WriteLine(shirtClass.ItemColor);  // "white"
   }
}

The compiler matches the signature of this initializer with the instance constructor in the base class that has a matching signature. Thus, when an instance of Shirt is created, it automatically calls the constructor in Apparel that has one parameter. This call is made before any code in Shirt is executed.

A second version of the initializer, one that uses the keyword this rather than base, also indicates which constructor is to be called when the class is instantiated. However, this refers to a constructor within the class instance, rather than the base class. This form of the initializer is useful for reducing the amount of compiler code generated in a class having multiple constructors and several fields to be initialized.

For example, if you examine the generated IL code for the following class, you would find that the fields fiberType and color are defined separately for each constructor.

public class Natural
{
   string fiberType = "Generic";
   string color = "white";
   // Constructors
   public Natural() { ... }
   public Natural(string cotton_type) { ... }
   public Natural(string cotton_type, string color) { ... }
}

For more efficient code, perform the field initialization in a single constructor and have the other constructors invoke it using the this initializer.

public Natural() {   // constructor initializes fields
    fiberType="Generic";
    color = "white";
}
// Following constructors use this() to call default
// constructor before constructor body is executed.
public Natural(string cotton_type): this() { ... }
public Natural(string cotton_type, string color):
   this() { ... }

Private Constructor

Recall that the private modifier makes a class member inaccessible outside its class. When applied to a class constructor, it prevents outside classes from creating instances of that class. Although somewhat non-intuitive (what good is a class that cannot be instantiated?), this turns out to be a surprisingly powerful feature.

Its most obvious use is with classes that provide functionality solely through static methods and fields. A classic example is the System.Math class found in the Framework Class Library.

It has two static fields, pi and the e (natural logarithmic base), as well as several methods that return trigonometric values. The methods behave as built-in functions, and there is no reason for a program to create an instance of the math class in order to use them.

In the earlier discussion of static methods, we presented a class (refer to Listing 3-5) that performs metric conversions. Listing 3-10 shows this class with the private constructor added.

Example 3-10. Private Constructor Used with Class Containing Static Methods

using System;
class Conversions
{
   // class contains functions to provide metric conversions
   // static method can only work with static field.
   static cmPerInch = 2.54;
   private static double gmPerPound = 455;
   public static double inchesToMetric(double inches) {
      return(inches * cmPerInch);
   }
   public static double poundsToGrams(double pounds) {
      return(pounds*gmPerPound);
   }
   // Private constructor prevents creating class instance
   private Conversions()
   { ...   }
}

Although a simple example, this illustrates a class that does not require instantiation: The methods are static, and there is no state information that would be associated with an instance of the class.

A natural question that arises is whether it is better to use the private constructor or an abstract class to prevent instantiation. The answer lies in understanding the differences between the two. First, consider inheritance. Although an abstract class cannot be instantiated, its true purpose is to serve as a base for derived classes (that can be instantiated) to create their own implementation. A class employing a private constructor is not meant to be inherited, nor can it be. Secondly, recall that a private constructor only prevents outside classes from instantiating; it does not prevent an instance of the class from being created within the class itself.

The traits of the private constructor can also be applied to managing object creation. Although the private constructor prevents an outside method from instantiating its class, it does allow a public method in the class (sometimes called a factory method) to create an object. This means that a class can create instances of itself, control how the outside world accesses them, and control the number of instances created. This topic is discussed in Chapter 4, “Working with Objects in C#.”

Static Constructor

Also known as a class or type constructor, the static constructor is executed after the type is loaded and before any one of the type members is accessed. Its primary purpose is to initialize static class members. This limited role results from the many restrictions that distinguish it from the instance constructor:

  • It cannot have parameters and cannot be overloaded. Consequently, a class may have only one static constructor.

  • It must have private access, which C# assigns automatically.

  • It cannot call other constructors.

  • It can only access static members.

Although it does not have a parameter, do not confuse it with a default base constructor, which must be an instance constructor. The following code illustrates the interplay between the static and instance constructors.

class BaseClass
{
   private static int callCounter;
   //  Static constructor
   static BaseClass(){
      Console.WriteLine("Static Constructor: "+callCounter);
   }
   //  Instance constructors
   public BaseClass()
   {
      callCounter+= 1;
      Console.WriteLine("Instance Constructor: "+callCounter);
   }
   // ... Other class operations
}

This class contains a static initializer, a static constructor, and an instance constructor. Let's look at the sequence of events that occur when the class is instantiated:

BaseClass myClass1 = new BaseClass();
BaseClass myClass2 = new BaseClass();
BaseClass myClass2 = new BaseClass();

Output:

Static Constructor: 0
Instance Constructor: 1
Instance Constructor: 2
Instance Constructor: 3

The compiler first emits code to initialize the static field to 0; it then executes the static constructor code that displays the initial value of callCounter. Next, the base constructor is executed. It increments the counter and displays its current value, which is now 1. Each time a new instance of BaseClass is created, the counter is incremented. Note that the static constructor is executed only once, no matter how many instances of the class are created.

Delegates and Events

Clicking a submit button, moving the mouse across a Form, pushing an Enter key, a character being received on an I/O port—each of these is an event that usually triggers a call to one or more special event handling routines within a program.

In the .NET world, events are bona fide class members—equal in status to properties and methods. Just about every class in the Framework Class Library has event members. A prime example is the Control class, which serves as a base class for all GUI components. Its events—including Click, DoubleClick, KeyUp, and GotFocus—are designed to recognize the most common actions that occur when a user interacts with a program. But an event is only one side of the coin. On the other side is a method that responds to, or handles, the event. Thus, if you look at the Control class methods, you'll find OnClick, OnDoubleClick, OnKeyUp, and the other methods that correspond to their events.

Figure 3-3 illustrates the fundamental relationship between events and event handlers that is described in this section. You'll often see this relationship referred to in terms of publisher/subscriber, where the object setting off the event is the publisher and the method handling it is the subscriber.

Event handling relationships

Figure 3-3. Event handling relationships

Delegates

Connecting an event to the handling method(s) is a delegate object. This object maintains a list of methods that it calls when an event occurs. Its role is similar to that of the callback functions that Windows API programmers are used to, but it represents a considerable improvement in safeguarding code.

In Microsoft Windows programming, a callback occurs when a function calls another function using a function pointer it receives. The calling function has no way of knowing whether the address actually refers to a valid function. As a result, program errors and crashes often occur due to bad memory references. The .NET delegate eliminates this problem. The C# compiler performs type checking to ensure that a delegate only calls methods that have a signature and return type matching that specified in the delegate declaration. As an example, consider this delegate declaration:

public delegate void MyString (string msg);

When the delegate is declared, the C# compiler creates a sealed class having the name of the delegate identifier (MyString). This class defines a constructor that accepts the name of a method—static or instance—as one of its parameters. It also contains methods that enable the delegate to maintain a list of target methods. This means that—unlike the callback approach—a single delegate can call multiple event handling methods.

A method must be registered with a delegate for it to be called by that delegate. Only methods that return no value and accept a single string parameter can be registered with this delegate; otherwise, a compilation error occurs. Listing 3-11 shows how to declare the MyString delegate and register multiple methods with it. When the delegate is called, it loops through its internal invocation list and calls all the registered methods in the order they were registered. The process of calling multiple methods is referred to as multicasting.

Example 3-11. Multicasting Delegate

// file: delegate.cs
using System;
using System.Threading;
class DelegateSample
{
   public delegate void MyString(string s);
   public static void PrintLower(string s){
      Console.WriteLine(s.ToLower());
   }

   public static void PrintUpper(string s){
      Console.WriteLine(s.ToUpper());
   }

   public static void Main()
   {
      MyString myDel;
      // register method to be called by delegate
      myDel =  new MyString(PrintLower);
      // register second method
      myDel += new MyString(PrintUpper);
      // call delegate
      myDel("My Name is Violetta.");
      // Output: my name is violetta.
      //         MY NAME IS VIOLETTA.
   }
}

Note that the += operator is used to add a method to the invocation list. Conversely, a method can be removed using the -= operator:

myDel += new MyString(PrintUpper); // register for callback
myDel -= new MyString(PrintUpper); // remove method from list

In the preceding example, the delegate calls each method synchronously, which means that each succeeding method is called only after the preceding method has completed operation. There are two potential problems with this: a method could “hang up” and never return control, or a method could simply take a long time to process—blocking the entire application. To remedy this, .NET allows delegates to make asynchronous calls to methods. When this occurs, the called method runs on a separate thread than the calling method. The calling method can then determine when the invoked method has completed its task by polling it, or having it call back a method when it is completed. Asynchronous calls are discussed in Chapter 13, “Asynchronous Programming and Multithreading.”

Delegate-Based Event Handling

In abstract terms, the .NET event model is based on the Observer Design Pattern. This pattern is defined as “a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.”[1] We can modify this definition to describe the .NET event handling model depicted in Figure 3-3: “when an event occurs, all the delegate's registered methods are notified and executed automatically.” An understanding of how events and delegates work together is the key to handling events properly in .NET.

To illustrate, let's look at two examples. We'll begin with built-in events that have a predefined delegate. Then, we'll examine how to create events and delegates for a custom class.

Working with Built-In Events

The example in Listing 3-12 displays a form and permits a user to draw a line on the form by pushing a mouse key down, dragging the mouse, and then raising the mouse key. To get the endpoints of the line, it is necessary to recognize the MouseDown and MouseUp events. When a MouseUp occurs, the line is drawn.

The delegate, MouseEventHandler, and the event, MouseDown, are predefined in the Framework Class Library. The developer's task is reduced to implementing the event handler code and registering it with the delegate. The += operator is used to register methods associated with an event.

this.MouseDown += new MouseEventHandler(OnMouseDown);

The underlying construct of this statement is

this.event += new delegate(event handler method);

To implement an event handler you must provide the signature defined by the delegate. You can find this in documentation that describes the declaration of the MouseEventHandler delegate:

public delegate void MouseEventHandler(
   object sender,
   MouseEventArgs e)

Example 3-12. Event Handler Example

using System;
using System.Windows.Forms;
using System.Drawing;
class DrawingForm:Form
{
   private int lastX;
   private int lastY;
   private Pen myPen= Pens.Black; // defines color of drawn line
   public DrawingForm() {
      this.Text = "Drawing Pad";
      // Create delegates to call MouseUp and MouseDown
      this.MouseDown += new MouseEventHandler(OnMouseDown);
      this.MouseUp   += new MouseEventHandler(OnMouseUp);
   }
   private void OnMouseDown(object sender, MouseEventArgs e)
   {
      lastX = e.X;
      lastY = e.Y;
   }
   private void OnMouseUp(object sender, MouseEventArgs e)
   {
      // The next two statements draw a line on the form
      Graphics g = this.CreateGraphics();
      if (lastX >0 ){
         g.DrawLine(myPen, lastX,lastY,e.X,e.Y);
      }
      lastX = e.X;
      lastY = e.Y;
   }
   static void Main() {
      Application.Run(new DrawingForm());
   }
}

Using Anonymous Methods with Delegates

.NET 2.0 introduced a language construct known as anonymous methods that eliminates the need for a separate event handler method; instead, the event handling code is encapsulated within the delegate. For example, we can replace the following statement from Listing 3-12:

this.MouseDown += new MouseEventHandler(OnMouseDown);

with this code that creates a delegate and includes the code to be executed when the delegate is invoked:

this.MouseDown += delegate(object sender, EventArgs e)
{
   lastX = e.X;
   lastY = e.Y;
}

The code block, which replaces OnMouseDown, requires no method name and is thus referred to as an anonymous method. Let's look at its formal syntax:

delegate [(parameter-list)] {anonymous-method-block}
  • The delegate keyword is placed in front of the code that is executed when the delegate is invoked.

  • An optional parameter list may be used to pass data to the code block. These parameters should match those declared by the delegate. In this example, the parameters correspond to those required by the predefined delegate MouseEventHandler.

  • When the C# compiler encounters the anonymous code block, it creates a new class and constructs a method inside it to contain the code block. This method is called when the delegate is invoked.

To further clarify the use of anonymous methods, let's use them to simplify the example shown earlier in Listing 3-11. In the original version, a custom delegate is declared, and two callback methods are implemented and registered with the delegate. In the new version, the two callback methods are replaced with anonymous code blocks:

// delegate declaration
public delegate void MyString(string s);

// Register two anonymous methods with the delegate
MyString myDel;
myDel =  delegate(string s) { Console.WriteLine(s.ToLower()); };
myDel += delegate(string s) { Console.WriteLine(s.ToUpper()); };
// invoke delegate
myDel("My name is Violetta");

When the delegate is called, it executes the code provided in the two anonymous methods, which results in the input string being printed in all lower- and uppercase letters, respectively.

Defining Custom Events

When writing your own classes, it is often necessary to define custom events that signal when some change of state has occurred. For example, you may have a component running that monitors an I/O port and notifies another program about the status of data being received. You could use raw delegates to manage the event notification; but allowing direct access to a delegate means that any method can fire the event by simply invoking the delegate. A better approach—and that used by classes in the Framework Class Library—is to use the event keyword to specify a delegate that will be called when the event occurs.

The syntax for declaring an event is

public event  <delegate name>  <event name>

Let's look at a simple example that illustrates the interaction of an event and delegate:

public class IOMonitor
{
   // Declare delegate
   public delegate void IODelegate(String s);
   // Define event variable
   public event IODelegate DataReceived ;
   // Fire the event
   public void FireReceivedEvent (string msg)
   {
      if (DataReceived != null)  // Always check for null
      {
         DataReceived(msg);      // Invoke callbacks
      }
   }
}

This code declares the event DataReceived and uses it in the FireReceivedEvent method to fire the event. For demonstration purposes, FireReceivedEvent is assigned a public access modifier; in most cases, it would be private to ensure that the event could only be fired within the IOMonitor class. Note that it is good practice to always check the event delegate for null before publishing the event. Otherwise, an exception is thrown if the delegate's invocation list is empty (no client has subscribed to the event).

Only a few lines of code are required to register a method with the delegate and then invoke the event:

IOMonitor monitor = new IOMonitor();
// You must provide a method that handles the callback
monitor.DataReceived += new IODelegate(callback method);
monitor.FireReceivedEvent("Buffer Full");  // Fire event

Defining a Delegate to Work with Events

In the preceding example, the event delegate defines a method signature that takes a single string parameter. This helps simplify the example, but in practice, the signature should conform to that used by all built-in .NET delegates. The EventHandler delegate provides an example of the signature that should be used:

public delegate void EventHandler(object sender,
                                  EventArgs eventArgs);

The delegate signature should define a void return type, and have an object and EventArgs type parameter. The sender parameter identifies the publisher of the event; this enables a client to use a single method to handle and identify an event that may originate from multiple sources.

The second parameter contains the data associated with the event. .NET provides the EventArgs class as a generic container to hold a list of arguments. This offers several advantages, the most important being that it decouples the event handler method from the event publisher. For example, new arguments can be added later to the EventArgs container without affecting existing subscribers.

Creating an EventArgs type to be used as a parameter requires defining a new class that inherits from EventArgs. Here is an example that contains a single string property. The value of this property is set prior to firing the event in which it is included as a parameter.

public class IOEventArgs: EventArgs
{
   public IOEventArgs(string msg){
      this.eventMsg = msg;
   }
   public string Msg{
      get {return eventMsg;}
   }
   private string eventMsg;
}

IOEventArgs illustrates the guidelines to follow when defining an EventArgs class:

  • It must inherit from the EventArgs class.

  • Its name should end with EventArgs.

  • Define the arguments as readonly fields or properties.

  • Use a constructor to initialize the values.

If an event does not generate data, there is no need to create a class to serve as the EventArgs parameter. Instead, simply pass EventArgs.Empty.

Core Note

Core Note

If your delegate uses the EventHandler signature, you can use EventHandler as your delegate instead of creating your own. Because it is part of the .NET Framework Class Library, there is no need to declare it.

An Event Handling Example

Let's bring these aforementioned ideas into play with an event-based stock trading example. For brevity, the code in Listing 3-13 includes only an event to indicate when shares of a stock are sold. A stock purchase event can be added using similar logic.

Example 3-13. Implementing a Custom Event-Based Application

//File: stocktrader.cs
using System;
// (1) Declare delegate
public delegate void TraderDelegate(object sender,
                                    EventArgs e);
// (2) A class to define the arguments passed to the delegate
public class TraderEventArgs: EventArgs
{
   public TraderEventArgs(int shs, decimal prc, string msg,
                          string sym){
      this.tradeMsg   = msg;
      this.tradeprice = prc;
      this.tradeshs   = shs;
      this.tradesym   = sym;
   }
   public string Desc{
      get {return tradeMsg;}
   }
   public decimal SalesPrice{
      get {return tradeprice;}
   }
   public int Shares{
      get {return tradeshs;}
   }
   public string Symbol{
       get {return tradesym;}
   }

   private string  tradeMsg;
   private decimal tradeprice;
   private int tradeshs;
   private string tradesym;
}
// (3) class defining event handling methods
public class EventHandlerClass
{
   public void HandleStockSale(object sender,EventArgs e)
   {
      // do housekeeping for stock purchase
      TraderEventArgs ev = (TraderEventArgs) e;
      decimal totSale = (decimal)(ev.Shares * ev.SalesPrice);
      Console.WriteLine(ev.Desc);
   }
   public void LogTransaction(object sender,EventArgs e)
   {
      TraderEventArgs ev = (TraderEventArgs) e;
      Console.WriteLine(ev.Symbol+" "+ev.Shares.ToString()
                        +" "+ev.SalesPrice.ToString("###.##"));
   }
}
// (4) Class to sell stock and publish stock sold event
public class Seller
{
   // Define event indicating a stock sale
   public event TraderDelegate StockSold;
   public void StartUp(string sym, int shs, decimal curr)
   {
      decimal salePrice= GetSalePrice(curr);
      TraderEventArgs t = new TraderEventArgs(shs,salePrice,
            sym+" Sold at "+salePrice.ToString("###.##"), sym);
      FireSellEvent(t);  // Fire event
   }
   // method to return price at which stock is sold
   // this simulates a random price movement from current price
   private decimal GetSalePrice(decimal curr)
   {
      Random rNum = new Random();
      // returns random number between 0 and 1
      decimal rndSale = (decimal)rNum.NextDouble() * 4;
      decimal salePrice= curr - 2 + rndSale;
      return salePrice;
   }
   private void FireSellEvent(EventArgs e)
   {
      if (StockSold != null)  // Publish defensively
      {
         StockSold(this, e);  // Invoke callbacks by delegate
      }
   }
}

class MyApp
{
   public static void Main()
   {
      EventHandlerClass eClass= new EventHandlerClass();
      Seller sell = new Seller();
      // Register two event handlers for stocksold event
      sell.StockSold += new TraderDelegate(
                    eClass.HandleStockSale);
      sell.StockSold += new TraderDelegate(
                    eClass.LogTransaction);
      // Invoke method to sell stock(symbol, curr price,
                                     sell price)
      sell.StartUp("HPQ",100, 26);
   }
}

The class Seller is at the heart of the application. It performs the stock transaction and signals it by publishing a StockSold event. The client requesting the transaction registers two event handlers, HandleStockSale and LogTransaction, to be notified when the event occurs. Note also how the TraderEvents class exposes the transaction details to the event handlers.

Operator Overloading

Built-in operators such as + and - are used so instinctively that one rarely thinks of them as a predefined implementation for manipulating intrinsic types. In C#, for example, the + operator is used for addition or concatenation depending on the data types involved. Clearly, each must be supported by different underlying code. Because the compiler knows what a numeric and string type represent, it is quite capable of doing this. It makes sense then that these operators cannot be applied to custom classes or structures of which the compiler has no knowledge.

It turns out that C# provides a mechanism referred to as operator overloading that enables a class to implement code that determines how the class responds to the operator. The code for overloading an operator is syntactically similar to that of a method:

public static <return type> operator <op> ( parameter list)
{ implementation code}

Several rules govern its usage:

  • The public and static modifiers are required.

  • The return type is the class type when working with classes. It can never be void.

  • op is a binary, unary, or relational operator. Both equals (==) and not equals (!=) must be implemented in a relational pair.

  • Binary operators require two arguments; unary operators require one argument.

Operator overloading with classes does not have to be limited to geometric or spatial objects. The example shown in Listing 3-14 demonstrates how to use the concept to maintain stocks in a portfolio. It contains two classes: one that represents a stock (defined by its price, number of shares, and risk factor) and one that represents the portfolio of stocks. Two overloaded operators (+ and -) add and remove stocks from the portfolio.

Example 3-14. Operator Overloading for Classes

using System;
class Portfolio
{
   public decimal risk;
   public decimal totValue;
   // Overloaded operator to add stock to Portfolio
   public static Portfolio operator + (Portfolio p,Stock s)
   {
      decimal currVal  = p.totValue;
      decimal currRisk = p.risk;
      p.totValue = p.totValue + s.StockVal;
      p.risk = (currVal/p.totValue)*p.risk +
         (s.StockVal/p.totValue)* s.BetaVal;
      return p;
   }
   // Overloaded operator to remove stock from Portfolio
   public static Portfolio operator - (Portfolio p,Stock s)
   {
      p.totValue = p.totValue - s.StockVal;
      p.risk = p.risk - ((s.BetaVal-p.risk)
                       *(s.StockVal/p.totValue));
      return p;
   }
}
class Stock
{
   private decimal value;
   private decimal beta; // risk increases with value
   public  Stock(decimal myBeta, decimal myValue,
                 int shares)
   {
      value = (decimal) myValue * shares;
      beta = myBeta;
   }
   public decimal StockVal
      { get {return value; } }

   public decimal BetaVal
      { get {return beta; } }
}
class MyApp
{
   public static void Main()
   {
      Portfolio p = new Portfolio();
      // 200 shs of HPQ at $25, 100 shs of IBM @ $95
      Stock hpq  = new Stock(1.1M, 25M, 200);
      Stock ibm  = new Stock(1.05M, 95.0M, 100);
      p += hpq;   // Add hpq
      p += ibm;   // Add ibm
      Console.Write("value:{0} ",p.totValue.ToString());
      Console.WriteLine(" risk: {0}",
                        p.risk.ToString("#.00"));
      // value = 14,500 and risk = 1.07
      p -= ibm;   // Remove ibm from portfolio
      Console.Write("value:{0} ",p.totValue.ToString());
      Console.Write(" risk: {0}",p.risk.ToString("#.00"));
      // value = 5000 and risk = 1.10
   }
}

The addition or deletion of a stock causes the portfolio total value and weighted risk factor to be adjusted. For example, when both stocks are added to the portfolio, the risk is 1.07. Remove ibm and the risk is 1.10, the risk of hpq alone.

When choosing to implement operator overloading, be aware that .NET languages are not required to support it. The easiest way to provide interoperability for those languages (such as Visual Basic.NET) lacking this feature is to include an additional class member that performs the same function:

public static Portfolio AddStocks ( Portfolio p, Stock s)
{ return p + s; }

In this case, the code exposes a public method whose implementation calls the overloaded method.

Another approach is to take advantage of the fact that language interaction occurs at the Intermediate Language level and that each operator is represented in the IL by a hidden method. Thus, if a language knows how to invoke this method, it can access the operator.

The ECMA standard provides a list of method names that correspond to each operator. For example, the + and & used in the preceding code are represented by op_Addition and op_BitwiseAnd, respectively. A language would access the overloaded + operator with its own syntactic variation of the following code:

newPortfolio = P.op_Addition(P, s)

Either approach works, but relying on assembly language is probably less appealing than providing a custom public method.

Although the discussion has been on classes, operator overloading also can be applied to simple data types. For example, you could define your own exponentiation operator for integers.

Interfaces

Syntax:

[attributes] [modifiers] interface  identifier [:baselist]
{interface body} [;]

The syntax of the interface declaration is identical to that of a class except that the keyword interface replaces class. This should not be surprising, because an interface is basically a class that declares, but does not implement, its members. An instance of it cannot be created, and classes that inherit from it must implement all of its methods.

This sounds similar to an abstract class, which also cannot be instantiated and requires derived classes to implement its abstract methods. The difference is that an abstract class has many more capabilities: It may be inherited by subclasses, and it may contain state data and concrete methods.

One rule of thumb when deciding whether to use a class or interface type is the relationship between the type and its inheriting classes. An interface defines a behavior for a class—something it “can do.” The built-in .NET ICloneable interface, which permits an object to create a copy of itself, is an example of this. Classes, on the other hand, should be used when the inheriting class is a “type of ” the base class. For example, you could create a shape as a base class, a circle as a subclass of shape, and the capability to change the size of the shape as an interface method.

Aside from their differing roles, there are numerous implementation and usage differences between a class and an interface:

  • An interface cannot inherit from a class.

  • An interface can inherit from multiple interfaces.

  • A class can inherit from multiple interfaces, but only one class.

  • Interface members must be methods, properties, events, or indexers.

  • All interface members must have public access (the default).

  • By convention, an interface name should begin with an uppercase I.

Creating and Using a Custom Interface

Listing 3-15 demonstrates how to define, implement, and program against a simple custom interface:

Example 3-15. Interface Basics

public interface IShapeFunction
{
   double GetArea();    //  public abstract method
}

class Circle : IShapeFunction   // Inherit Interface
{
   private double radius;
   public circle( double rad)
   {
      radius = rad;
   }
   public double GetArea()
   {
      return ( Math.PI*radius * radius);
   }
   public string ShowMe()
   {
      return ("Circle");
   }
}

class Rectangle: IShapeFunction      // Inherit interface
{
   private double width, height;
   public rectangle( double myWidth, double myHeight)
   {
      width= myWidth;
      height= myHeight;
   }
   public double GetArea()
   {
      return (width * height);
   }
}
class MyApp
{
   public static void Main()
   {
      Circle myCircle = new Circle(4);
      // Interface variable that references a circle object.
      IShapeFunction myICircle = myCircle;
      Rectangle myRectangle = new Rectangle(4,8);
      // Place shape instances in an array
      IShapeFunction[] myShapes = {myCircle, myRectangle};
      // The next two statements print the same results
      MessageBox.Show(myCircle.GetArea().ToString());
      MessageBox.Show(myShapes[0].GetArea().ToString());
      MessageBox.Show(myCircle.ShowMe());   // class method
      MessageBox.Show(myICircle.GetArea().ToString());
      // The following will not compile because myICircle can
      // access only members of the IShapeFunction interface.
      MessageBox.Show(myICircle.ShowMe());
   }
}

The declaration of the interface and implementation of the classes is straightforward: A Circle and Rectangle class inherit the IShapeFunction interface and implement its GetArea method.

Conceptually, it is useful to think of a class that implements one or more interfaces as having multiple types. It has its own innate type, but it also can be viewed as having the type of each interface it implements. Consider this code in the MyApp class:

Circle myCircle = new Circle(4);
IShapeFunction myICircle = myCircle;

The first statement creates an instance of the Circle class. The second statement creates a variable that refers to this circle object. However, because it is specified as an IShapeFunction type, it can only access members of that interface. This is why an attempt to reference the ShowMe method fails. By using the interface type, you effectively create a filter that restricts access to members of the interface only.

One of the most valuable aspects of working with interfaces is that a programmer can treat disparate classes in a similar manner, as long at they implement the same interface.

IShapeFunction[] myShapes = {myCircle, myRectangle};
MessageBox.Show(myShapes[0].GetArea().ToString());

This code creates an array that can contain any class that implements the IShapeFunction interface. Its only interest is in using the GetArea method, and it neither has nor requires any knowledge of other class members. We can easily extend this example to create a class to work with this array that has no knowledge of the Circle or Rectangle class.

public class ShapeUtil
{
   public static double SumAreas
         (IShapeFunction[] funArray)
   {
      double tot = 0.0;
      for (int i = 0; i < funArray.Length; i++)
         { tot += funArray[i].GetArea();  }
      return tot;
   }
}
// Access this method with ShapeUtil.SumAreas(myShapes);

The code in this class can be used with any concrete class that implements the IShapeFunction interface.

Core Approach

Core Approach

For maximum program flexibility, consider using interface types, rather than class types, when defining method parameters. This ensures there is no limit on the types of classes that can be passed to the method, as long as they implement the required interface.

Working with Interfaces

Determining Which Interface Members Are Available

You may not always know at compile time whether a class implements a specific interface. This is often the case when working with a collection that contains a number of types. To perform this check at runtime, use the as or is keyword in your code.

// (1)  as keyword to determine if interface is implemented
Circle myCircle = new Circle(5.0);
IShapeFunction myICircle;
myICircle = myCircle as IShapeFunction;
If (myICircle !=null) //interface is implemented

// (2)  is keyword to determine if interface is implemented
Circle myCircle = new Circle(5.0);
If (myCircle is IShapeFunction) // True if interface implemented

Accessing Interface Methods

Because a class may inherit methods from a base class and/or multiple interfaces, there is the possibility that inherited methods will have the same name. To avoid this ambiguity, specify an interface method declaration in the derived class with the interface and method name:

double IShapeFunction.GetArea() {  // <interface>.<method>

This not only permits a class to implement multiple methods, but has the added effect of limiting access to this method to interface references only. For example, the following would result in an error:

Circle myCircle = new Circle(5.0);
// cannot reference explicit method
double myArea = myCircle.GetArea();

Interfaces Versus Abstract Classes

In the overall design of a system, the real issue is not whether to use a class or interface, but how to best mix the two. The C# restriction on multiple implementation inheritance assures an expanded role for interfaces. It is just too burdensome to try to load all the needed functionality into one base class or a hierarchy of base classes.

A better solution is to define a base class and then expand its capabilities by adding corresponding interfaces. This permits developers to use the class directly without regard to whether the methods are interface implementations. Yet, it also permits the developer to make use of the interface and ignore the other class members.

Of course, you must consider the drawbacks to using interfaces. A well-designed base class reduces the burden on the inherited class to implement everything by promoting code reuse. Also, a non-abstract class can add a member without breaking any existing derived class; adding a member to an interface would break any class implementing the interface.

Generics

To understand and appreciate the concept of generics, consider the need to create a class that will manage a collection of objects. The objects may be of any type and are specified at compile time by a parameter passed to the class. Moreover, the collection class must be type-safe—meaning that it will accept only objects of the specified type.

In the 1.x versions of .NET, there is no way to create such a class. Your best option is to create a class that contains an array (or other container type) that treats everything as an object. As shown here, casting, or an as operator, is then required to access the actual object type. It is also necessary to include code that verifies the stored object is the correct type.

Object[] myStack = new object[50];
myStack[0] = new Circle(5.0);    // place Circle object in array
myStack[1] = "Circle";           // place string in array
Circle c1 = myStack[0] as Circle;
if( c1!=null) {                  // circle object obtained
Circle c2 = (Circle) myStack[1]; // invalid case exception

Generics, introduced with .NET 2.0, offer an elegant solution that eliminates the casting, explicit type checking, and boxing that occurs for value type objects. The primary challenge of working with generics is getting used to the syntax, which can be used with a class, interface, or structure.

The best way to approach the syntax is to think of it as a way to pass the data type you'll be working with as a parameter to the generic class. Here is the declaration for a generic class:

public class myCollection<T>
{
   T[] myStack = new T[50];
}

The type parameter T is placed in brackets and serves as a placeholder for the actual type. The compiler recognizes any reference to T within the body of the class and replaces it with the actual type requested. As this statement shows, creating an instance of a generic class is straightforward:

myCollection <string> = new myCollection<string>;

In this case, string is a type argument and specifies that the class is to work with string types only. Note that more than one type parameter may be used, and that the type parameter can be any name, although Microsoft uses (and recommends) single characters in its generic classes.

Although a class may be generic, it can restrict the types that it will accept. This is done by including an optional list of constraints for each type parameter. To declare a constraint, add the where keyword followed by a list of parameter/requirement pairs. The following declaration requires that the type parameter implement the ISerializable and IComparable interfaces:

public class myCollection<T> where
   T:ISerializable,
   T:IComparable

A parameter may have multiple interface constraints and a single class restraint. In addition, there are three special constraints to be aware of:

class—. Parameter must be reference type.

struct—. Parameter must be value type.

new()—. Type parameter must have a parameterless constructor.

An example of a generic class is provided in Listing 3-16. The class implements an array that manages objects of the type specified in the type parameter. It includes methods to add items to the array, compare items, and return an item count. It includes one constraint that restricts the type parameter to a reference type.

Example 3-16. A Generic Class to Hold Data

using System.Collections.Generic
public class GenStack<T>
   where T:class    // constraint restricts access to ref types
{
   private T[] stackCollection;
   private int count = 0;
   // Constructor
   public GenStack(int size)
   {
      stackCollection = new T[size];
   }
   public void Add(T item)
   {
      stackCollection[count] = item;
      count += 1;
   }
   // Indexer to expose elements in internal array
   public T this[int ndx]
   {
      get
      {
         if (!(ndx < 0 || ndx > count - 1))
            return stackCollection[ndx];
         // Return empty object
         else return (default(T));
      }
   }
   public int ItemCount
   {
      get {return count;}
   }
   public int Compare<C>(T value1, T value2)
   {
      // Case-sensitive comparison: -1, 0(match), 1
      return Comparer<T>.Default.Compare(value1, value2);
   }
}

The following code demonstrates how a client could access the generic class described in Listing 3-16:

// Create instance to hold 10 items of type string
GenStack<string> myStack = new GenStack<string>(10);
myStack.Add("Leslie");
myStack.Add("Joanna");
Console.WriteLine(myStack.ItemCount);  // 2
Console.WriteLine(myStack[1]);         // Joanna
int rel = myStack.Compare<string>("Joanna", "joanna");  // -1
Console.WriteLine(rel.ToString());

Generics and the .NET generic collection classes are discussed further in Chapter 4.

Structures

A .NET structure—struct in C# syntax—is often described as a lightweight class. It is similar to a class in that its members include fields, methods, properties, events, and constructors; and it can inherit from interfaces. But there are also implementation differences and restrictions that limit its capabilities vis-à-vis a class:

  • A struct is a value type. It implicitly inherits from the System.ValueType.

  • A struct cannot inherit from classes, nor can it be inherited.

  • An explicitly declared constructor must have at least one parameter.

  • struct members cannot have initializers. The field members must be initialized by the constructor or the client code that creates the constructor.

Because a struct is a value type, it is stored on the stack where a program works directly with its contents. This generally provides quicker access than indirectly accessing data through a pointer to the heap. On the downside, structs can slow things down when passed back and forth as parameters. Rather than passing a reference, the CLR copies a struct and sends the copy to the receiving method. Also, a struct faces the boxing and unboxing issue of value types.

When deciding whether to use a struct to represent your data, consider the fact that types that naturally have value semantics (objects that directly contain their value as opposed to a reference to a value) are often implemented underneath as a struct. Examples include the primitives discussed in Chapter 2, the color structure whose properties (red, aqua, and so on) define colors in .NET, the DateTime structure used for date-related operations, and various graphics structures used to represent objects such as points and rectangles.

Defining Structures

Syntax:

[attribute][modifier] struct identifier [:interfaces]
{struct-body}

Example:

public struct DressShirt
{
   public float CollarSz;
   public int SleeveLn;
   // constructor
   public DressShirt(float collar, int sleeve)
   {
      this.CollarSz = collar;
      this.SleeveLn = sleeve;
   }
}

The syntax clearly resembles a class. In fact, replace struct with class and the code compiles. It has a public modifier that permits access from any assembly. The default modifier is internal, which restricts access to the containing assembly. No interfaces are specified—although a struct can inherit from an interface—so the struct is not required to implement any specific methods.

Core Note

Core Note

It is possible to specify the layout of a struct's fields in memory using the StructLayout attribute. This is most commonly used when the struct is to be accessed by unmanaged code that expects a specific physical layout of the data. By default, the fields in a struct are stored in sequential memory locations.

An instance of the structure can be created in two ways:

DressShirt dShirt = new DressShirt(16, 33);
DressShirt myShirt = new DressShirt();
myShirt.CollarSz = 16.5F;
myShirt.SleeveLn = 33;

The first statement creates an instance that relies on the user-defined constructor to initialize the field values. When designing such a constructor, be aware that you must initialize all fields within the constructor; otherwise, the compiler will issue an error.

The second statement also creates an instance of the struct by calling the default constructor, which initializes the fields to default values of zero (0). Note the difference here between a struct and a class: A struct always has a default parameterless constructor; a class has the default parameterless constructor only if there are no explicitly defined constructors.

Using Methods and Properties with a Structure

Methods and properties are usually associated with classes, but they play an equally important role in the use of structures. In fact, a client accessing a method has no syntactical clue as to whether the method or property is associated with a class or struct. Listing 3-17 extends the original example to add two properties and a method.

Example 3-17. Basic Elements of a Struct

public struct DressShirt
{
   private float CollarSz;
   private int SleeveLn;
   public DressShirt(float collar, int sleeve)
   {
      this.CollarSz = collar;
      this.SleeveLn = sleeve;
   }

   // Properties to return sleeve and collar values
   public int Sleeve
   {
      get {return (SleeveLn);}
      set {SleeveLn = value;}
   }
   public float Collar
   {
      get {return (CollarSz); }
      // "value" is an implicit parameter
      set {CollarSz = value;   }
   }

   // Method to convert size to different scale
   public string ShirtSize()
   {
      string mySize = "S";
      if (CollarSz > 14.5) mySize = "M";
      if (CollarSz > 15.5) mySize = "L";
      if (CollarSz > 16.5) mySize = "XL";
      return (mySize);
   }
}

The most important thing to note about this code is that it could be cut and pasted into a class and run with no changes. Although a struct doesn't support all of the features of a class, the ones it does are implemented using identical syntax.

Structure Versus Class

Many developers instinctively select a class to represent data and the operations performed on it. However, there are cases where a struct is a better choice, as evidenced by its use in the Framework Class Library to represent simple data types. This section compares the two and offers general guidelines to consider when choosing between the two.

Table 3-5. Comparison of Structure and Class

 

Structure

Class

Default access level of the type

Internal

Internal

Default access level for data members

Public

Private

Default access level for properties and methods

Private

Private

Value or reference type

Value

Reference

Can be a base for new types

No

Yes

Implement interfaces

Yes

Yes

Raise and handle events

Yes

Yes

Scope of members

Structure

Class

Instance initialization

Constructor—with or without parameters. A struct cannot contain a custom parameterless constructor.

Constructor—with or without parameters.

Can be nested

Yes

Yes

Has a destructor

No

Yes (Finalizer)

It is clear from the table that structures possess many of the features and capabilities of classes. Consequently, a developer may have difficulty deciding which is the better choice. The answer lies in understanding the few—but significant—differences between the two.

Structures Are Value Types and Classes Are Reference Types

As mentioned, classes are allocated space from the managed heap when the object or class instance is created. The address of the object (on the heap) is returned to the variable representing the object. In contrast, a variable set to a struct type contains the structure's actual data—not a pointer. The ramifications of this are most pronounced when passing arguments to functions. Reference types simply require that a pointer be passed, whereas structures require that a copy of all fields be made and passed to the function.

The structure does have some advantages with regard to memory allocation and Garbage Collection. Structure instances are allocated in a thread's stack rather than the managed heap, thus avoiding the associated overhead of managing pointers. Memory management is also simpler. When a copy of a structure is no longer reachable, its memory is collected and made available. In contrast, classes often require special code to handle unmanaged resources or invoke the system's garbage collection routine. Garbage Collection is covered in the next chapter.

Unlike a Class, a Structure Cannot Be Inherited

This is a departure from C++ conventions that permit a class to use an existing structure as a base class and permit a structure to use a class as a base. Is the .NET lack of support for this a step backward? Not really. It better delineates the role of the structure versus the class—making the structure less of a pseudo-class and more of a data structure. In addition, it provides for a more efficient implementation of the structure. The restriction enables the compiler to minimize the amount of administrative code that would be required to support inheritance. For example, because the compiler knows that a structure's methods cannot be overridden by subclasses, it can optimize the method invocation by expanding the method code inline rather than executing a method call.

General Rules for Choosing Between a Structure and a Class

The easiest way to make the choice is to compare the features of the type that you are designing with the following checklist. If any of these are true, you should use a class.

  • The type needs to serve as a base for deriving other types.

  • The type needs to inherit from another class. Note that although a structure cannot explicitly specify a base class, it does implicitly inherit from the System.ValueType and may override the inherited methods.

  • The type is frequently passed as a method parameter. Performance degrades as copies of the structure are created with each call. An exception to this is when the structure exists inside an array. Because an array is a reference type, only a pointer to the array is passed.

  • The type is used as the return type of methods. In the case where the return type is a structure, the system must copy the structure from the called function to the calling program.

Summary

The goal of the C# language architects was to create a “component-oriented” language based on the traditional object-oriented principles of encapsulation, inheritance, and polymorphism. Toward this end, they included language features that make properties, events, and attributes first-class language constructs. Properties now have their own get and set syntax; events can be created with the event keyword and linked to delegates that call registered methods when the event occurs; custom or built-in attributes can be attached to a class or selected class members to add descriptive information to an assembly's metacode.

C# provides several forms of method declarations: a virtual method permits a derived class to implement its own version of the method; sealed prevents a derived class from overriding it; and abstract requires a derived class to implement its own version of the method.

In some cases, a structure or interface provides a better programming solution than a class. The C# struct is an efficient, simple way to represent self-contained data types that don't require the overhead of classes. An interface is a practical way to define a behavior that can be passed on to inheriting classes. In .NET, its value is enhanced because a class (or struct) can inherit from any number of interfaces—but from only one class.

Test Your Understanding

1:

What type of class cannot be inherited?

2:

How many classes can a class directly inherit? How many interfaces?

3:

Referring to the following class, answer the questions below:

public class ShowName {
   public static void ShowMe(string MyName)
   { Console.WriteLine(MyName); }
}
  1. Can the method ShowName be referenced from s?

    ShowName s = new ShowName();
    s.ShowMe("Giacomo");
    
  2. Write a code sample that will print “My Name is Ishmael”.

4:

Can an abstract class have non-abstract methods?

5:

What keyword must a derived class use to replace a non-virtual inherited method?

6:

What are the results from running the following code?

public class ParmTest
{
   public static void GetCoordinates(ref int x, int y)
   {
      x= x+y;
      y= y+x;
   }
}
// calling method
int x=20;
int y=40;
ParmTest.GetCoordinates(ref x, y);
Console.WriteLine("x="+x+" y="+y);
  1. x=60 y=40
    
  2. x=20 y=60
    
  3. x=20 y=40
    
  4. x=60 y=60
    

7:

What is the best way to ensure that languages that do not recognize operator overloading can access C# code containing this feature?

8:

Name two ways that you can prevent a class from being instantiated.

9:

Your application contains a class StoreSales that fires an event when an item is sold. The event provides the saleprice (decimal), date (DateTime), and the itemnum (int) to its subscribers. Create an event handler that processes the ItemSold event by extracting and printing the sales data.

public delegate void SaleDelegate(object sender,
                                  SaleEvArgs e);
public event SaleDelegate ItemSold;
StoreSales mysale= new StoreSale();
mySale.ItemSold += new SaleDelegate(PrintSale);

10:

What happens if you attempt to compile and run the following code?

using System;
public class Base
{
   public void aMethod(int i, String s)
   {
      Console.WriteLine("Base Method");
   }
   public Base()
   {
      Console.WriteLine("Base Constructor");
   }
}

public class Child: Base
{
   string parm="Hello";
   public static void Main(String[] argv)
   {
      Child c = new Child();
      c.aMethod();
   }
   void aMethod(int i, string Parm)
  {
      Console.WriteLine(Parm);
   }
   public void aMethod()
   { }
}
  1. Error during compilation.

  2. “Base Constructor” is printed.

  3. “Base Constructor” and “Base Method” are printed.

  4. “Base Constructor” and “Hello” are printed.



[1] Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides; Addison-Wesley, 1995.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset