Journal Articles

CVu Journal Vol 16, #4 - Aug 2004 + Programming Topics
Browse in : All > Journals > CVu > 164 (12)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Introduction to C# - Part 2

Author: Administrator

Date: 03 August 2004 13:16:06 +01:00 or Tue, 03 August 2004 13:16:06 +01:00

Summary: 

Welcome to the second in a series of articles introducing the C# programming language. In the previous issue the basics of the language such as variables, methods and classes were covered. In this issue classes are covered in more detail highlighting an important feature called inheritance.

Body: 

Welcome Back

Welcome to the second in a series of articles introducing the C# programming language. In the previous issue the basics of the language such as variables, methods and classes were covered. In this issue classes are covered in more detail highlighting an important feature called inheritance. If you are just tuning in, and have some programming experience, this article should still be digestible.

Classes, Objects and Types

A Touch of Class

One of the most common descriptions of classes that I have found is, "A class is a template for creating objects frequently used to model real world entities such as a bank account or automobile." This is an excellent and concise description of a class but for me it helped to relate the idea of an "object template" to something I was already familiar with. Another place templates are found is in word processing applications and as it turns out this is a good analogy.

Document templates specify fonts for different sections, nice borders and graphics as well as the overall layout. A resume template might define sections for contact information, objective, work history and education, including appropriate formatting for headings and body text. When I need to whip up a new resume I open my word processing application and create a new document from the resume template. Now all that's left to do is fill in the information that is unique to me and I have a shiny new resume.

A class can also be created that specifies the different sections, borders, graphics and layout for a resume.

public class Resume {
  public string Name, PhoneNumber, Objective;
  ...
  public void Print() {
    ...
  }
  ...
}

This Resume class contains string member variables that store text for the same sections that the document template provides. It also has a Print method that might print the resume to the screen for review or to a printer.

Now that we have our template we follow a process similar to creating our resume with the word processor. First we need to create a new object from the class just as we created a new document from the word processor's resume template.

Resume myResume = new Resume();

Now that we have our handy new object we need to fill in our information.

myResume.Name = "Mike Bergin";
myResume.PhoneNumber = "555-2234";
myResume.Objective = "Make tons of money!";

The resume now contains all of my relevant personal information so now it's time to print.

myResume.Print();

There are a few important points to note about this example. The first point is that when we modify an object, or document, it does not modify the template. The second point is if we create two objects, or two documents, from the same template, changes to one of them are not reflected in the other. For example if I enter my name into one object, or document, it does not appear in yours, the two are unique entities.

What Type of Person Are You?!

Classes are also called types. This use of the word type is similar to how people use it to categorize each other. For example a person confronting someone they are upset with might say, "I didn't know you were that type of person." They are referring to the actions, feelings or thoughts of someone, just as calling a member variable a type is a categorization of the methods, actions, and data of that piece of software. Assuming that the argument is between a wife and her cheating husband the following example provides a simple representation of "that type of person" in software.

class Cheater {
  string Name, SpouseName, MistressName;
  void PerformUnfaithfulAct() {
    // Outside the scope of this article
  }
}

This snippet of code defines a new class, or type, named Cheater containing three string member variables and one method. Instances of this class could be created to represent all the cheating husbands of the world, all created from the same template, but each unique.

void CreateCheatingHusbands() {
  Cheater bob = new Cheater();
  bob.Name = "Bob Smith";
  Cheater john = new Cheater();
  john.Name = "John Doe";
}

Inheritance

Consider a scenario where you need to write a textbook describing each star in the solar system for an advanced astronomy course. The descriptions must be extremely precise, covering information that might even change during the lifetime of the book due to new discoveries in modern science. The first thing you would most likely do is identify the characteristics shared by all stars. Next you might categorize the different types of stars based on some common characteristics, for example by size and temperature grade. Now that the generic characteristics and groups of similar stars have been identified you might begin writing.

The first chapters of your book would most likely cover the common characteristics of all stars, which you identified in the initial phases of analysis. These chapters would contain very general information such as the chemical elements that stars are composed of, however it would not contain the amounts because they are different for each star. Next you would pick a category of star, such as white dwarfs, describing only what makes that group of stars unique. You would be careful not to provide any redundant information simply by referring back to previous sections. Once you had covered each category you would cover each specific star, providing information about what makes it unique among its group such as its name, coordinates and precise chemical composition.

As new discoveries are made some of the information in the textbook may become invalid. For example an astronomer may discover a new element present in all stars. Updating your textbook to reflect this information would be relatively simple because the topic of composition was only covered once and then referred back to, so the text only needs to be updated in one place. Another side effect of this approach is that it is much quicker to describe each star in relation to a set of common characteristics instead of describing each in isolation.

Source code is essentially a description of the data and behavioural composition of an application. When a program is written using a language that allows software to be expressed by modelling real world entities, such as C#, developers follow the same basic process we did in writing the textbook. In order to illustrate this let's apply the same steps to the development of an address book application used to manage contact information.

The first step is to identify the common characteristics of all contacts managed by the application. We perform our analysis by examining a few examples of contacts the application will need to manage. One example is our Aunt Marilyn, who we find has a name, phone number address, birthday and email address. Another example we examine is our favourite pizza restaurant, and we find that it has a name, phone number, address and website. Comparing these two examples we find that they both have a name, phone number and address. Now that we have identified the characteristics common to all contacts we can write this description in C#.

public class Contact {
  public string Name, PhoneNumber, Address;
}

The next step that we took in writing our textbook was to identify groups of similar items so we do that next. By performing our analysis of the characteristics shared by all contacts we have actually identified these groups, people and businesses. Before writing our description of these two groups we must keep in mind that we don't want to provide any redundant information, we only provide what makes them unique.

public class Person : Contact {
  public string EmailAddress, BirthDay;
}

public class Business : Contact {
  public string WebSite;
}

You may notice that the name of the Contact class follows the name of each new class separated by a colon. In C# this notation is used to indicate that the reader should refer to another section of the source code for more information. In this case we are saying that the description of Person is the sum of the description provided and the description of Contact. In our textbook we would have appended a superscript numeral to a word and then indicated at the bottom of the page that the reader is to refer back to a particular section to provide this same indication.

The sun is composed1 of 91.2% Hydrogen, 8.7% Helium … 1. Refer back to Chapter 12 Composition for more information.

Now that we have identified the common characteristics of all contacts and the groups, let's review exactly what we have. First we found that all contacts have a name, phone number and address. Once we had identified these traits we described them in the Contact class. The next product of our analysis was the identification of two groups of contacts, namely people and businesses. We found that all people have an email address and birthday in addition to what was already described in the Contact class, and described this in the Person class. Businesses were found to all have a website in addition to what was already described in the Contact class, so we described this in the Business class. Whew! Now that we have the hard part done its time to describe each unique entry in our address book.

In software written in C#, classes are used to describe the groups of items we are working with and objects are used to represent the members of those groups. So in this particular case we now create instances of the Person class for each person in our address book and instances of the Business class for each business.

Person auntMarilyn = new Person();
auntMarilyn.Name = "Marilyn Bergin";
auntMarilyn.EmailAddress =
                  "marilyn@somewhere.com";
...
Business pizzaPlaza = new Business();
pizzaPlaza.Name = "Pizza Plaza";
pizzaPlaza.WebSite = "www.pizzaplaza.com";
...

The execution of the code in the last snippet deserves a bit of explanation so let's run through it line by line to see what's happening. On the first line a new instance of the Person class is created and stored in a variable named auntMarilyn. The next line stores the string Marilyn Bergin in the Name member variable of the auntMarilyn object. When the computer reads this line of code it looks at the Person class for a description of a member variable named Name. The computer doesn't find it there but the class indicates that the computer should also look at the Contact class for more information, where it does find a member variable named Name. Next the string marilyn@somewhere.com is stored in a member variable named EmailAddress. Again the computer looks back to the Person class for a description of a member variable and this time it finds it. The same process is followed for the lines that deal with the instance of the Business class. The computer looks in the Business class for information and if it doesn't find it refers to the Contact class as indicated.

Inheritance is the ability to describe a section of code in relation to another section of code. As we saw in the Person and Business classes, C# provides the inheritance notation, the colon followed by the name of the generic class, to indicate that everything from that class should be pulled into a new class that also adds a few items of its own. So when working with an instance of the Person class we know it not only contains the member variables defined in its own class definition but also those defined in the Contact class's definition as well. This is referred to as one class inheriting from another class. In this scenario the Contact class is referred to as the base class or superclass and the Person class is called the subclass or derived class.

Inheritance is an extremely powerful feature that provides a number of benefits when used properly. The two main aspects of inheritance to consider are that it cuts down on the sheer amount of source code and that many classes can inherit from the same class. Cutting down on the amount of source code helps us to realize the one of the big promises of OOP, code reuse. When we reuse code we cut down on development time because we don't need to write as much, and maintenance is also cut down, which has been shown to be more expensive than development in many cases. The second point to note is that many classes can share the same base class, and as we will see in the following sections this is another extremely powerful aspect of inheritance.

There is a fair amount of terminology used when discussing inheritance and software that supports this feature so before we continue it may be helpful to provide some definitions. As we have seen a class that is inherited from is referred to as a base class or a superclass. A class that inherits from another is called a derived class or a subclass. Also in both of these scenarios the word type might be used interchangeably with the word class such as derived type instead of derived class. When a class inherits from another class the act of doing so is referred to as subclassing. Inheriting from another and providing additional members or other customizations is called specialization. You don't need to commit all of these to memory, just know they are here if you see some confusing jargon later.

Leveraging Inheritance

Now that we have explored the basic concept of inheritance and how it is supported by C# we can move on to some other features that are implemented through the use of inheritance.

Polymorphism

Polymorphism is the ability of a variab le to reference an instance of the variable's delcared type or a subclass of it. For example, a variable of type Contact could be used to store an instance of either the Person or Business classes.

Contact contact = new Person();

When an instance of a subclass is referenced using a variable of its base class's type, only the members declared in the base type are accessible. In the preceding example if we had tried to reference the EmailAddress member variable we would have received an error when compiling the code. The reasons for this are purely mechanical; only instances of Contact or one its subclasses may be stored in the variable because we are only guaranteed that the members defined in the Contact class are supported.

This makes sense because Person inherits all members defined in the Contact class so it exposes the same methods, properties, variables, etc. One catch to this is that only the members defined by the variable's type may be used no matter what type of object is actually stored in the variable. Simply put, if we store an instance of the Person class in a Contact variable we can only access members declared in the Contact class. You might be thinking that the compiler knows we are storing a Person object in the variable because the code is on the same line. This is a valid point in this situation but consider if we used polymorphism when calling a method.

public void Close(Contact contact) { ... }
public void Main() {
  Close(new Person());
}

In the body of the Close method we couldn't know what type had actually been passed in, just that it supports all of the members defined in the Contact class. This is compounded by the fact that code might be introduced at a later time that calls this method, passing an instance of a class that didn't exist when the code containing the Close method was written.

Specialization

Inheritance allows us to pull one class's implementation into another, which as we've seen can be extremely useful. There are many times however when the inherited implementation doesn't quite meet our needs, luckily C# allows us to effectively replace inherited members with our own implementations. Not all members may be replaced, C# requires class authors to declare that a member may be replaced using the virtual keyword. Subclasses may replace virtual members by providing one with the same signature but replacing virtual with the override keyword.

public class Contact {
  public virtual void Save(Connection conn) {
    // Save member variable values
  }
}

public class Person : Contact {
  public override void Save(Connection conn) {
    base.Save();
    // Save extended member variable values
  }
}

In this code snippet we added a new virtual method named Save to the Contact class that saves its member variables. Classes that inherit this method must replace it with their own, which saves any new members they introduce in addition to the inherited members. In order to simplify this task, C# allows subclasses to explicitly refer to members of their superclass using the base keyword, even when it's been overridden. The base keyword allows subclasses to not just replace a member but to reuse it, in effect customizing the existing member for its specific purpose.

Combining Polymorphism and Specialization

Polymorphism and specialization can be combined in powerful ways to develop extremely dynamic software. Here we will make use of the new Save method introduced in the previous example in an attempt to showcase some of the power available to C# programmers.

public void Persist(Contact contact) {
  Connection conn = GetConnection();
  Contact.Save(conn);
  CloseConnection(conn);
}

public static void Main() {
  Person auntMarilyn = new Person();
  auntMarilyn.EmailAddress =
                    "marilyn@somewhere.com";
  ...
  Business pizzaPlaza = new Business();
  pizzaPlaza.WebSite = "www.pizzaplaza.com";
  ...
  Persist(auntMarilyn);
  Persist(pizzaPlaza);
}

In the example above we defined a method named Persist that saves any instance of the Contact class or one of its subclasses. When the instance of the Person class is passed to the Persist method the Save method implemented in the Person class is called, causing all of its variables to be saved. When the instance of the Business class is passed however, only the variables declared in the Contact class are saved because the Business class did not customize the Save method to include the variables it introduced. As we can see the Persist method doesn't care what the actual type stored in the variable is, only that it supports the Save method.

Interfaces

The term interface refers to the mechanisms provided to interact with a class, such as the member variables, methods and properties it exposes.

An interface is declared in much the same way as a class is declared, except that it does not provide any implementation, the parts between the opening { and closing }. A class can declare that it implements a particular interface and then provide implementations of all the interface's members. Variables may declare that they hold instances of a particular interface just as a variable may declare that it contains an instance of a particular class. Interfaces cannot be instantiated, but classes that implement the interface may be stored in one of these variables.

In the section on polymorphism the Contact class defined a method named Save that saved the values stored in the object's variables to a database. Instead of this method being declared in the Contact class it could have been declared in an interface instead. This might be the case if you were using a third party persistence framework for example. The interface provided by the third party framework would be implemented by objects that need to be persisted, and the framework would do the rest. In this scenario the Contact class would most likely implement the interface and provide default implementations for each interface member. Let's assume the interface from the persistence framework is defined as follows.

public interface IPersistable {
  public void Save(Connection conn);
  public void Read(Connection conn);
}

Assuming this is the interface required by the persistence framework, we would need to make a few changes to our classes if they are to take advantage of this new persistence framework. First we would change the Contact class to implement the interface.

public class Contact : IPersistable {

  ...

  public void Save(Connection conn) {
    // Save to the database...
  }

  public void Read(Connection conn) {
    // Read from the database...
  }
}

There are a few points to note about this example. First, the notation used to declare that a class implements an interface is exactly the same as for declaring inheritance. If a class both inherits from a class and implements an interface then the interface name follows the base class name separated by a comma. For example if the Contact class inherited from a class named Widget then it would be modified as shown below.

public class Contact : Widget, IPersistable {
  ...
}

It is legal for a single class to implement multiple interfaces, in which case each additional interface would be listed in the same comma delimited fashion. Interface members implemented by a class are implicitly virtual. Now that we have implemented this interface in our Contact class, let's look at the changes we need to make to the Person class.

public class Person : Contact {
  public override void Save(Connection conn) {
    base.Save(conn);
    // Save members defined in Person
  }

  public override void Read(Connection conn) {
    base.Read(conn);
    // Read members defined in Person;
  }
}

Notice that we did not explicitly declare that the Person class implements the IPersistable interface. The reason for this is that interface implementation is transitive, meaning that because the Person class inherits from the Contact class it implicitly implements the interface, so declaring it again would be redundant. In the methods that override the inherited implementations of the interface members we again make use of the base class's implementation, being careful to not reproduce any code we can reuse.

Now that we have examined how an interface is implemented, let's take a look at how these changes might be used in the persistence framework. The following code snippet might exist in a class that facilitates the persistence of objects using the framework.

public class PersistenceManager {
  public void Persist(IPersistable subject) {
    Connection conn = GetConnection();
    Subject.Save(conn);
    ReleaseConnection(conn);
  }
}

This illustrates that the important point that interfaces, like inheritance, allow for polymorphism. The PersistenceManager class doesn't care how the Save method is implemented by the instance passed into the method, or even what type it actually is. All that matters is that the parameter implements the proper interface.

Abstract Classes

Abstract classes are a mix between using inheritance to reuse code and leveraging polymorphism with interfaces. An abstract class may contain both concrete members and interface style members that don't declare any implementation. The concrete members are inherited by derived classes just as normal methods are and can use the virtual/override keywords in the same way as well. The interface style members require that subclasses provide an implementation for them, just as implementing an interface require the implementers to do. Essentially that's all there is to it, so let's go over a quick example.

The example we reviewed in the section on interfaces could have also been implemented using abstract classes. Implementing the persistence framework using inheritance allows more power to be embedded into the Contact class itself. In many cases frameworks that use abstract classes instead of interfaces take this approach and sometimes are able to minimize the amount of work on the part of the user of the framework. In this new example we will be inheriting from an abstract class defined as shown below.

public abstract class DatabaseObject {
  public void Save(Connection conn) { ... }
  public void Read(Connection conn) { ... }
  public abstract bool IsDirty();
  public abstract void ClearDirty();
}

The declaration of this class includes a keyword that we haven't encountered before, abstract. The abstract keyword must appear in the declaration of a class that has any abstract members. This same keyword appears in the two interface style member declarations IsDirty and ClearDirty. Just like with interfaces, no implementation is provided, requiring subclasses to provide it. The abstract keyword must be used in these declarations because this is not an interface, so it must be clearly stated if a member is abstract.

In this new framework the Save and Read methods are actually implemented for us, all that we as the users of the framework need to do is implement a method that indicates if the object is dirty, meaning that data has been modified since it was last saved to the database. The framework might periodically check to see if the IsDirty method returns true and if so call the Save method, followed by a call to the ClearDirty method to indicate that the values have been saved. Tracking when changes were made to the object is left up to the object itself, however they are now automatically saved to the database by simply returning true from a method call. In closing, the following example shows how the Contact class might look if it used this inheritance based framework.

public class Contact : DatabaseObject {

  public bool hasChanged;

  public override bool IsDirty() {
    return hasChanged;
  }

  public override void ClearDirty() {
    hasChanged = false;
  }
}

See You Next Time

In this issue we discussed some of the fundamental features of classes. In the next issue we will cover a few more class-related features, as well as the type system supported by C#. If you read the first article and are wondering what happened to the Address Book application, we will eventually get to incorporate everything we've covered. See you next time!

Notes: 

More fields may be available via dynamicdata ..