Topics in This Chapter
Defining a Class: The attributes and modifiers included in a class definition influence the behavior and accessibility of the class.
Constants: A const
type defines fixed values at compilation time.
Fields: A field is typically used to maintain data inside a class.
Properties: A property is the recommended way to expose a class's data.
Methods: The functionality of a class is defined by its methods.
Inheritance: By using inheritance, a class can take advantage of preexisting types.
Constructors: This special purpose method is used to initialize a class.
Events and Delegates: A program's actions are often triggered by events; delegates have the role of invoking methods to handle an event.
Operator Overloading: Operators can be used to manipulate classes.
Interfaces: An inherited interface defines the methods and properties that a struct
or class must implement.
Generics: By creating a generic class to store data, it is possible to eliminate casting, boxing, and ensure type safety.
Structures: In 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.
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.
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.
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.
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")]
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.
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.
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.
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.
| A class can be accessed from any assembly. |
| 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. |
| Access is limited to classes in the same assembly. This is the default access. |
| Applies only to a nested class. Access is limited to the container class. |
| The only case where multiple modifiers may be used. |
| Access is limited to the current assembly or types derived from the containing class. |
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.
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
.
| 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. |
| 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. |
| Specifies that a class contains only static members (.NET 2.0). |
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
.
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).
// .. 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.
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).
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:
|
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. |
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.
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.
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.
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.
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 |
---|---|
| 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 |
| 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.
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
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;}} }
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:
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.
value
is an implicit parameter containing the value passed when a property is called.
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.
An indexer is often referred to as a parameterized property. Like a property, it is declared within a class, and its body may contain get
and set
accessors that share the same syntax as property accessors. However, an indexer differs from a property in two significant ways: It accepts one or more parameters, and the keyword this
is used as its name. Here is the formal syntax:
Syntax:
[attributes] <modifier><return type> this[parameter(s)] {
Example:
public int this [int ndx] {
Note: The static
modifier is not supported because indexers work only with instances.
In a nutshell, the indexer provides a way to access a collection of values maintained within a single class instance. The parameters passed to the indexer are used as a single- or multi-dimensional index to the collection. The example in Listing 3-4 should clarify the concept.
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 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.
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 |
---|---|
| 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 |
| Designates that the method can be overridden in a subclass. This cannot be used with |
| 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 |
| 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. |
Prevents a derived class from overriding this method.
| |
| The method contains no implementation details and must be implemented by any subclass. Can only be used as a member of an |
| Indicates that the method is implemented externally. It is generally used with the |
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)
.
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" } }
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”.
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()
}
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.
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:
FillArray
is invoked to initialize the array. This requires passing the array as a parameter with the out
modifier.
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.
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.
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.
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.
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 Appare
l 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.
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() { ... }
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#.”
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.
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.
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.”
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.
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()); } }
.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.
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
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
.
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.
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.
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.
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.
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
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();
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.
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.
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.
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.
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.
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.
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 | 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.
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.
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.
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.
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.
[1] Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides; Addison-Wesley, 1995.