Common Programming Paradigms

In this section, we explore some common programming paradigms under .NET

Client–Server Programming

Software engineering, to a large extent, is about writing software such that generic code can be reused in multiple applications. Let's rewrite the very first program we wrote. This time we factor out the logic for greeting the user. The intent is to provide the ability to reuse this greeting code in any other application.

We first develop a console-based application.

Console-Based Greeting

Here is our new class that implements the greeting logic:

// Project ReusableCode, File ConsoleGreeting.cs

using System;

namespace MyGreeting {
     class ConsoleGreeting {

       private String m_userName;

       public void Greet() {
         Console.WriteLine("Hello " + m_userName);
       }

       public String UserName {
         set {
           m_userName = value;
         }
         get {
           return m_userName;
         }
       }
     }
}

This class defines a method, Greet, which displays a greeting to the console. To make things slightly more interesting, I have scoped this class into a namespace, MyGreeting.

Here is our new implementation of MyApp class, revised to use the ConsoleGreeting class. The changes have been highlighted.

//Project ReusableCode, File HelloUser.cs
using System;
using MyGreeting;

class MyApp
{
     public static void Main() {
       ConsoleGreeting obj = new ConsoleGreeting();
								obj.UserName = "Jay";
								obj.Greet();
     }
}

First we create an instance of class ConsoleGreeting. Once an object is obtained, we set the user name on the object and invoke the method Greet on the object.

Let's build the application. Assuming the greeting code has been saved in ConsoleGreeting.cs, here is the command line to build the application:

csc.exe HelloUser.cs ConsoleGreeting.cs

Compile and run the program. The results should be the same as the one from our previous run (Project HelloUser), only now we have some reusable code in the form of file ConsoleGreeting.cs. This file can potentially be used with any other application being developed. As a software vendor, you can even sell this source code to other companies that can use it to build their own applications. Admittedly, there is not much meat in our code to be a sellable product, but it does illustrate the point.

Reusing a piece of software logic at the source code level, however, has one major problem. Let's say a bug is found in the reusable code after all the applications using this code have been shipped. Once you fix the bug, there is no way to field-replace just the fix. Your only choice is to build all the dependent applications once again and ship them to the customers.

The crux of the problem is that the reusable code has been linked and absorbed in the final executable during the compilation process. Once the executable has been created, any change in the reusable code does not get reflected in the already generated executable.

We need to get away from such static linking of reusable code. If we could package the reusable code as a separate library that can be linked to the application during runtime, then field-replacing such a library would be relatively easy. Obviously, the runtime has to support such “dynamic” linking of a library when the application is executed.

Fortunately, the common language runtime provides a mechanism to load a library dynamically as and when an application needs to use it. As you may have guessed, such a library is called an assembly. Even the very first program that we wrote used such an assembly—MSCorLib.Dll. If a bug is found in, for example, Console.WriteLine, all that Microsoft has to do is fix the bug and field-replace MSCorLib.Dll, with no need for us to rebuild our application(s).

An assembly that provides the software logic that can be used by other applications is loosely referred to as a server. An application using such a service is called a client.

Let's use our MyGreeting class to build a library assembly. Here is the modified code excerpt for the class:

// Project ClientServer

using System;

namespace MyGreeting {
     public class ConsoleGreeting {
       private String m_userName;

       public void Greet() {
         Console.WriteLine("Hello " + m_userName);
       }

       ...
     }
}

Note that we had to specify keyword public on the class. Without this access modifier on the class, the client application will not be able to access any methods from the class, even if the methods themselves are marked as public.

Here is the command line to build the library assembly:

csc.exe -t:library ConsoleGreeting.cs

Option –t:library tells the compiler to build a library assembly. The generated output file is named ConsoleGreeting.dll.

The client code requires no changes. The only change is in the command-line parameters; we need to let the compiler know that our client code references ConsoleGreeting.dll, as shown here:

csc.exe -t:exe -r:ConsoleGreeting.dll HelloUser.cs

Build and run the client application. For now, make sure that Hello-User.exe and ConsoleGreeting.dll are both in the same directory. In a later chapter, we look at how an assembly can be placed in a location such that multiple applications can access it.

By breaking a monolithic application into client and server assemblies, we achieved our goal of field-replacing just the broken assembly without requiring a change to any other assemblies.

There is yet another problem that we solved inadvertently—the problem of reusing software logic across programming language boundaries. Take a look at ConsoleGreeting.cs—the code is written in C#. If you wish to reuse this code in another programming language, such as Visual Basic .NET, it is practically impossible. The Visual Basic .NET compiler cannot possibly compile C# source code and link it with Visual Basic .NET code. Thanks to the common language runtime, however, it is easy to reuse assemblies written in one language to be reused from assemblies written in another language. The following Visual Basic .NET code, for example, shows how to reuse the code from ConsoleGreeting.dll:

Imports MyGreeting

Module MyVBApp

     Sub Main()
       Dim obj As New ConsoleGreeting()
       obj.UserName = "Jay"
       obj.Greet()
     End Sub

End Module

The .NET SDK ships with a command-line tool, vbc.exe, to build Visual Basic .NET applications. Assuming this code is saved in MyVBApp.vb, the following command line shows how to compile MyVBApp.vb to build MyVBApp.exe, our client application:

vbc.exe -t:exe -r:ConsoleGreeting.dll MyVBApp.vb

The reason this cross-language operability is possible under .NET is because all the .NET compilers generate output in the same intermediate form, MSIL. In this respect, any programming language under .NET is a first-class language. The choice of a programming language is more of a preference issue.

Let's now develop a server that displays a greeting using the graphical user interface (GUI).

Windows-Based Greeting

The .NET Framework provides a class, MessageBox, under the namespace System.Windows.Forms that provides a static method Show to display a message box to the user. Using this method, the code for our new server can be written as follows:

using System;
using System.Windows.Forms;

namespace MyGreeting {
     public class WindowsGreeting {
       public void Greet() {
         MessageBox.Show("Hello " + m_userName);
       }
       ...
     }
}

Displaying the greeting using MessageBox isn't much of a challenge. Let's write code to create our own window.

The goal of our exercise is to create a dialog box with a greeting label and a “Close” button, as shown in Figure 2.1.

Figure 2.1. A simple dialog box.


The .NET Framework provides a class, Form (namespace System.Windows.Forms) to represent a window displayed in an application. This class can be used to create non-modal as well as modal windows (e.g., a dialog box). The class provides many properties and methods to control various aspects of the window such as appearance, size, and so on.

Typically, you extend the Form class to customize it for your needs, so let's define a new class that inherits from Form class. The code excerpt is shown here:

// Project WindowsGreeting

using System.Windows.Forms;

public class WindowsGreeting : Form {
     ...
}

The .NET Framework provides the label control in the form of the Label class and the button control in the form of the Button class. Both these classes are defined under the namespace System.Windows.Forms. We would need to create an instance of each of these classes. The code excerpt is shown here:

public class WindowsGreeting : Form {
     private Label m_label;
     private Button m_btnClose;

     // Initialization in the constructor
     public WindowsGreeting() {
       m_label = new Label();
       ...

       m_btnClose = new Button();
       ...

     }
}

The controls need to be positioned and sized properly. The framework provides a class Point to specify a location and a class Size to specify a size. These classes are defined under the namespace System.Drawing. The following code excerpt uses these classes to control the appearance of our controls:

m_label.Location = new Point(16, 16);
m_label.Size = new Size(136, 24);
...

m_btnClose.Location = new Point(48, 50);
m_btnClose.Size = new Size(56, 24);
...

The text for the button control needs to be set:

m_btnClose.Text = "Close";

We now need to add the logic to close the form when the user clicks the button control.

The button control exposes an event Click (you probably expected that) with a delegate of type EventHandler. Here is its definition:

public delegate void EventHandler(Object sender, EventArgs e);

Flexible Event Handler

The EventHandler delegate takes two parameters. The first parameter specifies the sender of the event. The second parameter can be used to pack any event-specific arguments. This makes EventHandler so flexible that all the standard events in the .NET class library use it. In theory, one can write a generic event handler that can accept virtually any event raised by the class library.


Let's ignore the parameters for the moment as they are of no current interest to us. Here is our code to handle the button click, based on the EventHandler definition:

void CloseButton_Click(Object sender, EventArgs e) {
     this.Close(); // close the form
}

Let's associate this method with the button click event:

m_btnClose.Click += new EventHandler(CloseButton_Click);

The controls have to be added to the form before they can be displayed. Form class exposes a property called Controls that can be used to add the controls, as shown here:

this.Controls.Add(m_label);
this.Controls.Add(m_btnOK);
...

We now need to set the dialog box window to a reasonable size:

this.ClientSize = new Size(150, 90);

Finally, although not needed for our demonstration, we will add a small enhancement to our dialog box. We will let the users press the Esc key to close the dialog box.[1] The Form class provides a property, CancelButton, that can be set to a specific button, as shown here:

[1] Those of us who prefer using the keyboard over the mouse appreciate this.

this.CancelButton = m_btnClose;

With this change, when the user presses Esc, CloseButton_Click gets invoked and the form closes.

We are done with the dialog box initialization code. Here is the initialization code in its entirety:

public WindowsGreeting() {
     // Create the label control and specify settings
     m_label = new Label();
     m_label.Location = new Point(16, 16);
     m_label.Size = new Size(136, 24);
     m_label.Text = "";

     // Create the button control and specify settings
     m_btnClose = new Button();
     m_btnClose.Location = new Point(48, 50);
     m_btnClose.Size = new Size(56, 24);
     m_btnClose.Text = "Close";
     m_btnClose.Click += new EventHandler(CloseButton_Click);

     // Add the controls to the dialog
     this.Controls.Add(m_label);
     this.Controls.Add(m_btnClose);

     // Dialog settings
     this.ClientSize = new Size(150, 90);

     // set ESC to work as cancel button
     this.CancelButton = m_btnClose;
}

There is just one more piece of business to be taken care of; we need a way to specify the user name for the greeting to be displayed. Let's define a property on the class called UserName. All we need is a set accessor on this property. The code excerpt is shown here:

public class WindowsGreeting : Form {
     ...
     public String UserName {
       set {
         m_label.Text = "Hello " + value;
       }
     }
     ...
}

We are done. Compile and build the assembly. Call it WindowsGreeting.dll. Here is the command line:

csc.exe -t:library 
     -r:System.Windows.Forms.dll WindowsGreeting.cs

Let's now write a client to use this class. Here is our new code for the client:

using System;
using MyGreeting;

class MyApp
{
     public static void Main() {
       WindowsGreeting dlg = new WindowsGreeting();
       dlg.UserName = "Jay";
       dlg.ShowDialog();
     }
}

Build this application using the following command line:

csc.exe -t:winexe -r:WindowsGreeting.dll HelloUser.cs

Option –t:winexe tells the compiler to build a GUI application.

Run this program. It should bring up a form similar to one shown in Figure 2.1. Click the Close button (or press Esc), and the form should disappear.

Interface-Based Programming

In the previous section, we developed a console-based server and a corresponding client. We then developed a Windows-based server and a corresponding client. Why can't we write just one client that can pick up a specified server implementation during runtime? Well, we can. Let's see how.

For our experiment, we see if there is any command-line parameter passed to our client program during execution. If there is any parameter present, we assume that the user intends to display a console greeting. If no parameters are detected, we display a Windows greeting.

So far in our examples, the entry point of the program, Main, did not have any arguments. The common language runtime accepts many overloaded method definitions for Main, which we examine in Chapter 4. There is one overloaded definition of Main that takes a single argument. If this overloaded method is used instead, then the common language runtime packages all the command-line parameters as an array of strings and passes it to the function.

Here is the new implementation of Main for the client program:

// Project MultipleServers

public static void Main(String[] args) {
     String userName = "Jay";

     bool bConsole = (args.Length >= 1);
     if (bConsole) {
       // console dialog
       ConsoleGreeting obj = new ConsoleGreeting();
       obj.UserName = userName;
       obj.Greet();
     }else {
       WindowsGreeting dlg = new WindowsGreeting();
       dlg.UserName = userName;
       dlg.ShowDialog();
     }
}

Looking at the code, there is a philosophical issue we need to address. Both ConsoleGreeting and WindowsGreeting have the same purpose—to display a greeting. However, one thing that stands out from the code is that there is no consistency in the way they are used. For example, ConsoleGreeting requires method Greet to be called, whereas WindowsGreeting requires method ShowDialog to be called. If in the future you define another class that implements a newer greeting mechanism, it probably will define a different method signature[2] to display the greeting.

[2] The signature of a method is the combination of the name of the method, the parameters it has, and its return type.

It would be nice to somehow formalize the behavior so that each class can implement this behavior in its own way but the client gets to treat all the classes in the same fashion. Fortunately, the common language runtime provides support for formalizing behavior in an abstraction called an interface.

Interfaces

An interface is simply a special type that consists of a set of methods. Unlike normal types, however, the methods are all pure virtual; that is, they do not have any implementation. Any other class that wishes to expose the behavior described by the interface can inherit from the interface and provide its own implementation. As a matter of fact, a class can inherit from as many interfaces as it desires. This support by the common language runtime for multiple interface inheritance more or less compensates for the single implementation inheritance restriction on a class.

Note that an interface can also define properties and indexers (after all, they all map to methods anyway). Also, an interface itself can inherit from one or more interfaces.

In C#, the keyword interface is used to define an interface. Using this keyword, the interface to greet a user can be defined as follows:

// Project Interfaces

// File Greeting.cs

using System;

namespace MyGreeting {
     public interface IGreeting {
       String UserName { set; }
       void Greet();
     }
}

Note that the interface name is prefixed with an I. This is a convention that the programming community has adopted to differentiate it from classes and other types.

As with classes, scoping an interface within a namespace is optional. If not explicitly scoped, the interface belongs to the global namespace.

Note that an interface can specify the type of accessor required on a property. Interface IGreeting mandates that a set accessor be implemented on property UserName.

Based on this interface, our ConsoleGreeting class can be modified as follows:

public class ConsoleGreeting : IGreeting {
     private String m_userName;

     public String UserName {
       set {
         m_userName = value;
       }
     }

     public void Greet() {
       Console.WriteLine("Hello " + m_userName);
     }
}

Interfaces versus Abstract Classes

The common language runtime (and therefore C#) also defines the notion of abstract methods and abstract classes. An abstract method is equivalent to pure virtual method in C++; that is, it does not have an implementation. An abstract class is a class that cannot be instantiated. An abstract class contains one or more abstract methods.

At first glance, an abstract class looks very similar to an interface. However, there are subtle differences between the two that you need to be aware of. First, although you can derive your class from more than one interface, you cannot derive it from more than one abstract class. Second, an abstract class may implement some functionality, whereas interfaces do not implement any functionality. Interfaces make you think in terms of interfaces—pure protocols to enforce black box communication between independent parties.

When designing your application, use interfaces to define interaction between different components and use abstract classes to define hierarchical relationships.


Similarly, our WindowsGreeting class can be redefined as follows:

public class WindowsGreeting : Form, IGreeting {
     ...
     public String UserName {
       set {
         m_label.Text = "Hello " + value;
       }
     }

     public void Greet() {
       this.ShowDialog();
     }
}

The following client-side code excerpt shows how to obtain an interface from a specific implementation:

public static IGreeting GetGreeting(bool bConsole) {
     Object obj;
     if (bConsole) {
       obj = new ConsoleGreeting();
     }else {
       obj = new WindowsGreeting();
     }
     return (IGreeting) obj;
}

As can be seen from the code, obtaining the desired interface from an object is as simple as typecasting the object to the interface.

What if the object doesn't support the requested interface? In this case, the common language runtime raises an exception of type InvalidCastException.

Remember that for more information on .NET defined types (e.g., InvalidCastException), you can look up the SDK documentation.

Test an Object's Type

Under C#, there are two more methods of checking if an object supports an interface. The first method is by using the keyword as, as shown here:

IGreeting greet = obj as IGreeting

If variable greet is not null, it implies that the object supports interface IGreeting:

if (greet != null) {
  // do something with greet
}

The second method is by using the keyword is, as shown here:

if (obj is IGreeting) {
  // do something with obj
}

Note that the keywords as and is are not just limited to interfaces; they can be used to check if an object can be converted to any other type. This is different than casting in -that there is no exception thrown if the object is not compatible with the specified type.


The client-side main code can be rewritten as follows:

public static void Main(String[] args) {
     String userName = "Jay";
     bool bConsole = (args.Length >= 1);
     IGreeting greetObj = GetGreeting(bConsole);
     greetObj.UserName = userName;
     greetObj.Greet();
}

As can be seen, once a specific implementation has been selected, the client can use it in the same consistent manner.

Interface-based programming drives down the separation of behavior from implementation and is an important programming paradigm. It forces the clients to think in terms of interaction and not implementation. Get familiar with this paradigm. The .NET class library defines many interfaces that we use during the course of the book.

Using Interfaces or Classes?

Those who have programmed in COM know that there is no way for a client to talk to a server other than using the interfaces. .NET, however, offers class-based communication as well as interface-based communication. This raises an interesting question: What should the client use? A class or an interface? Or, what should a server expose? Classes or interfaces?

Interfaces are good paradigms for exposing generic functionality that can potentially have multiple implementations. If you think various implementations can be expressed in terms of some generic functionality, then interfaces make sense. For example, most collection classes (e.g., Array, ArrayList, etc.) under .NET provide a mechanism to iterate through each item in the collection. This functionality is nicely expressed through an interface called IEnumerator. As we will see later in the book, .NET defines quite a few interfaces, each of which express a different functionality.

Exposing a class makes sense if a single implementation can be reused. A good example is the System.Windows.Forms class that we saw earlier. It captures most of the Windows GUI functionality in just one implementation. Developers can customize their user-interface behavior by just extending this class.


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

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