C#.NET: Introduction for Developers

Links

Introducing the .NET Framework

The .NET framework is an all-encompassing framework for development and management of Microsoft applications. The basic structure of the .NET framework is outlined in the diagram below. The foundation is a runtime environment, called the CLR, based on the underlying services provided by Microsoft Windows. All .NET code is compiled to an intermediate bytecode called MSIL, which is then interpreted by the CLR (or compiled via just-in-time aka JIT technology at runtime). A variety of foundation classes, collectively known as the FCL, are built on top of the runtime environment. The foundation classes in the FCL in turn serve as a basis for additional libraries for data access, XML processing, Web Services, and Form-Based programming. Microsoft makes the functionality in .NET mavailable via a variety of programming languages, including VB.NET, C#.NET, and C++.NET. Third parties have developed addional languages for the .NET framework, including Perl and Python. Regardless of the programming language used, the underlying framework is the same. Microsoft has also incorporated many of its existing applications into the overall .NET architecture. For example, Active Directory for directory services, MS-DTC for transaction management, and MSMQ for messaging. SQL Server, Exchange Server, and IIS all have .NET-friendly APIs.

VB.NETC#.NETManaged C++.NET etc...
Web ServicesWeb FormsWindows Forms
Data and XML Classes
(ADO.NET, SQL, XML, XSLT, XPath, etc...)
Framework Base Classes
(IO, string, net, security, threading, text, reflection, collections, etc...
Common Language Runtime
(debug, exception, type checking, JIT)
Windows Platform

Installation

You can download the .NET framework separately, without Visual Studio.NET for free. It is also included automatically as part of Visual Studio.NET. So far, to the best of my knowledge there are not any enterprise-strength third-party IDEs for .NET, so Visual Studio.NET is probably the best bet for real-world programming. However, for smaller applications, one can at least in theory either use a plain text editor or third party .NET editor, such as SharpDevelop ( http://sourceforge.net/projects/sharpdevelop), which is free.

Getting Started

C# is generally considered to be the core language in the .NET family of programming languages. Its syntax is similar to C++ and Java, and like Java it runs inside a virtual machine (the CLR), supports just-in-time (JIT) compilation, exceptions (though not checked exceptions) and performs automatic garbage collection. C#, like Java, also supports the single model of inheritance rather than the multiple inheritance available in C++. However, C# supports features available in C++ and not Java such as operator overloading, allows objects to be assigned to the stack as well as to the heap (value and reference types respectively), and has features such as enumerated types, structs, etc.

Hello World:

Below is a listing for the Hello World program. We will code it, compile, and run it using both the Visual Studio.NET and the command line.

	class HelloWorld
	{
		static void Main()
		{
			System.Console.WriteLine("Hello, world!");
		}
	}
	

The command line compilation program for C# is called csc.exe. It is located in C:\Windows\Microsoft.NET\Framework\vx.x e.g. C:\Windows\Microsoft.NET\Framework\v1.1.4322

Exercise: Modify the simple HelloWorld program to print a name passed in as a parameter on the command line, e.g. "Hello, John!"

Exercise: Use the online debugger to inspect variables and step through code in the HelloWorld program.

Issues to discuss:

C# Language Basics

Classes And Objects

Exercise: Create a simple class to model a car's state and behaviour See page 63, Programming C#, 3rd Edition.

Inheritance and Polymorphism

Arrays and Indexers

See page 163, Programming C#, 3rd Edition. Arrays are the simplest and most commonly used data structures for managing lists of items.

Collections

See page 190, Programming C#, 3rd Edition. The .NET framework provides a rich suite of collections classes, including Array, ArrayList, NameValueCollection, StringCollection, Queue, Stack, BitArray, Hashtable, Dictionary, etc...

Events and Delegates

See page 265, Programming C#, 3rd Edition. A delegate lets you pass a function as a parameter. The type safety of delegates requires the function you pass as a delegate to have the same signature as the delegate declaration. Review example below, from the MSDN library.

		// bookstore.cs
		using System;

		// A set of classes for handling a bookstore:
		namespace Bookstore
		{
		   using System.Collections;

		   // Describes a book in the book list:
		   public struct Book
		   {
			  public string Title;        // Title of the book.
			  public string Author;       // Author of the book.
			  public decimal Price;       // Price of the book.
			  public bool Paperback;      // Is it paperback?

			  public Book(string title, string author, decimal price, bool paperBack)
			  {
				 Title = title;
				 Author = author;
				 Price = price;
				 Paperback = paperBack;
			  }
		   }

		   // Declare a delegate type for processing a book:
		   public delegate void ProcessBookDelegate(Book book);

		   // Maintains a book database.
		   public class BookDB
		   {
			  // List of all books in the database:
			  ArrayList list = new ArrayList();

			  // Add a book to the database:
			  public void AddBook(string title, string author, decimal price, bool paperBack)
			  {
				 list.Add(new Book(title, author, price, paperBack));
			  }

			  // Call a passed-in delegate on each paperback book to process it:
			  public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
			  {
				 foreach (Book b in list)
				 {
					if (b.Paperback)
					// Calling the delegate:
					   processBook(b);
				 }
			  }
		   }
		}

		// Using the Bookstore classes:
		namespace BookTestClient
		{
		   using Bookstore;

		   // Class to total and average prices of books:
		   class PriceTotaller
		   {
			  int countBooks = 0;
			  decimal priceBooks = 0.0m;

			  internal void AddBookToTotal(Book book)
			  {
				 countBooks += 1;
				 priceBooks += book.Price;
			  }

			  internal decimal AveragePrice()
			  {
				 return priceBooks / countBooks;
			  }
		   }

		   // Class to test the book database:
		   class Test
		   {
			  // Print the title of the book.
			  static void PrintTitle(Book b)
			  {
				 Console.WriteLine("   {0}", b.Title);
			  }

			  // Execution starts here.
			  static void Main()
			  {
				 BookDB bookDB = new BookDB();

				 // Initialize the database with some books:
				 AddBooks(bookDB);

				 // Print all the titles of paperbacks:
				 Console.WriteLine("Paperback Book Titles:");
				 // Create a new delegate object associated with the static
				 // method Test.PrintTitle:
				 bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));

				 // Get the average price of a paperback by using
				 // a PriceTotaller object:
				 PriceTotaller totaller = new PriceTotaller();
				 // Create a new delegate object associated with the nonstatic
				 // method AddBookToTotal on the object totaller:
				 bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
				 Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
					totaller.AveragePrice());
			  }

			  // Initialize the book database with some test books:
			  static void AddBooks(BookDB bookDB)
			  {
				 bookDB.AddBook("The C Programming Language",
					"Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
				 bookDB.AddBook("The Unicode Standard 2.0",
					"The Unicode Consortium", 39.95m, true);
				 bookDB.AddBook("The MS-DOS Encyclopedia",
					"Ray Duncan", 129.95m, false);
				 bookDB.AddBook("Dogbert's Clues for the Clueless",
					"Scott Adams", 12.00m, true);
			  }
		   }
		}
		

Events are managed using delegates. Screen controls, such as buttons produce events. When an event occurs, the control uses a delegate to process that event.

First one creates a delegate for change notifications:

    // A delegate type for hooking up change notifications.
	public delegate void ChangedEventHandler(object sender, EventArgs e);
Next, one defines an event associated with this delegate.
    class ListWithNotification
    {
        //...other stuff
        public event ChangedEventHandler Changed;
Next, one raises the even when appropriate, e.g.
        //... ListWithNotification continued
        public override int Add(object value)
        {
            int i = base.Add(value);
            Changed(this, EventArgs.Empty);
            return i;
        }
Next, one adds a specific handler matching the delegate signature to the event:
    class EventListener
    {
        private void ListChangedHandler(object sender, EventArgs e)
        {
            Console.WriteLine("This is called when the event fires.");
        }
Finally, one adds this handler to the list of handlers the event will notify when it fires:
        //...EventListener continued
        this.list = list;
        // Add "ListChanged" to the Changed event on "List".
        list.Changed += new ChangedEventHandler(ListChangedHandler);
In the main program, one creates an instance of the list, then creates the handler for that list, and finally uses the list.

The complete listing is below:

	using System;
	namespace MyCollections
	{
	   using System.Collections;

	   // A delegate type for hooking up change notifications.
	   public delegate void ChangedEventHandler(object sender, EventArgs e);

	   // A class that works just like ArrayList, but sends event
	   // notifications whenever the list changes.
	   public class ListWithNotification: ArrayList
	   {
		  // An event that clients can use to be notified whenever the
		  // elements of the list change.
		  public event ChangedEventHandler Changed;

		  // Override some of the methods that can change the list;
		  // invoke event after each
		  public override int Add(object value)
		  {
			 int i = base.Add(value);
			 Changed(this, EventArgs.Empty);
			 return i;
		  }

		  public override void Clear()
		  {
			 base.Clear();
			 Changed(this, EventArgs.Empty);
		  }

		  public override object this[int index]

		  {
			 set
			 {
				base[index] = value;
				Changed(this, EventArgs.Empty);
			 }
		  }
	   }
	}

	namespace TestEvents
	{
	   using MyCollections;

	   class EventListener
	   {
		  private ListWithNotification list;

		  public EventListener(ListWithNotification list)
		  {
			 this.list = list;
			 // Add "ListChanged" to the Changed event on "List".
			 list.Changed += new ChangedEventHandler(ListChangedHandler);
		  }

		  // This will be called whenever the list changes.
		  private void ListChangedHandler(object sender, EventArgs e)
		  {
			 Console.WriteLine("This is called when the event fires.");
		  }

		  public void Detach()
		  {
			 // Detach the event and delete the list
			 list.Changed -= new ChangedEventHandler(ListChangedHandler);
			 list = null;
		  }
	   }

	   class Test
	   {
		  // Test the ListWithNotification class.
		  public static void Main()
		  {
		  // Create a new list.
		  ListWithNotification list = new ListWithNotification();

		  // Create a class that listens to the list's change event.
		  EventListener listener = new EventListener(list);

		  // Add and remove items from the list.
		  list.Add("item 1");
		  list.Clear();
		  listener.Detach();
		  }
	   }
	}
    

Unit Testing with Nunit

TDD, or Test Driven Development, popularized by Kent Beck in his book Extreme Programming Explained, is an idea that is gaining momentum in the software development community. The basic priciple is that any actual program code must be preceded by an automated test before it can be written. There are several reasons for this approach to development: NUnit is a tool in the xUnit family of unit testing tools that allows one to develop and execute unit tests. One must download the NUnit framework and reference nunit.framework.dll in one's application. From there, writing tests is straighforward, e.g.
namespace MyApplicationNamespace {

	using System;
	using NUnit.Framework;

	[TestFixture]
	public class TestHellowWorld
	{
		[Test] public void DefaultHelloWorldMessage()
		{
			HelloWorld hello = new HelloWorld();
			Assert.AreEqual("Hello, World!", hello.GetMessage());
		}

		[Test] public void ParameterizedHelloWorldMessage()
		{
			HelloWorld hello = new HelloWorld("John");
			Assert.AreEqual("Hello, John!", hello.GetMessage());
		}
	}

	}//end namespace
Exercise: Develop a hangman game using TDD and NUnit.

Programming with Forms and Controls

See page 309, Programming C#, 3rd Edition. Windows Forms allow you to develop applications with the standard Microsoft Windows look and feel. Below is a code listing for a simple form.
    using System;
    using System.Windows.Forms;

    namespace HelloWorld
    {

    public class HelloWorld : Form
    {
    	private System.Windows.Forms.Label helloWorldMessage;
    	private System.Windows.Forms.Button cancelButton;

    	public HelloWorld()
    	{
    		helloWorldMessage = new Label();
    		cancelButton = new Button();

    		this.Text = "Hello World";

    		helloWorldMessage.Location = new System.Drawing.Point(16,24);
			helloWorldMessage.Text = "Hello, World!";
			helloWorldMessage.Size = new System.Drawing.Size(216,24);

			cancelButton.Location = new System.Drawing.Point(150, 200);
			cancelButton.Size = new System.Drawing.Size(112,32);
			cancelButton.Text = "&Cancel";

			cancelButton.Click += new System.EventHandler(this.CancelButtonHandler);

			AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			ClientSize = new System.Drawing.Size(300, 300);

			Controls.Add(helloWorldMessage);
			Controls.Add(cancelButton);

    	}

    	protected void CancelButtonHandler (object Sender, System.EventArgs e)
    	{
    		Application.Exit();
    	}

    	public static void Main()
    	{
    		Application.Run(new HelloWorld());
    	}

    }

    }//end namespace

    
Below is another listing for a very simple drawing program.
    using System;
	using System.Drawing;
	using System.Collections;
	using System.ComponentModel;
	using System.Windows.Forms;
	using System.Data;

	namespace GraphicalCS
	{


	   /// 
	   /// Summary description for Form1.
	   /// 
	   public class MainWindow : System.Windows.Forms.Form
	   {
	      internal System.Windows.Forms.Button EraseAll;
	      internal System.Windows.Forms.Button FillWithHotPink;
	      internal System.Windows.Forms.Button AddFilledRectangle;
	      internal System.Windows.Forms.Button AddHollowRectangle;
	      internal System.Windows.Forms.Button AddPoint;
	      internal System.Windows.Forms.PictureBox Drawing;
	      internal System.Windows.Forms.GroupBox GroupBox1;
	      internal System.Windows.Forms.TextBox FilledCircleRadius;
	      internal System.Windows.Forms.Label Label3;
	      internal System.Windows.Forms.TextBox FilledCircleY;
	      internal System.Windows.Forms.Label Label2;
	      internal System.Windows.Forms.TextBox FilledCircleX;
	      internal System.Windows.Forms.Label Label1;
	      internal System.Windows.Forms.Button AddHollowCircle;
	      internal System.Windows.Forms.Button AddFilledCircleAt;
	      internal System.Windows.Forms.Button AddFilledCircle;
	      /// 
	      /// Required designer variable.
	      /// 
	      private System.ComponentModel.Container components = null;

	      public MainWindow()
	      {
	         //
	         // Required for Windows Form Designer support
	         //
	         InitializeComponent();

	         //
	         // TODO: Add any constructor code after InitializeComponent call
	         //
	      }

	      /// 
	      /// Clean up any resources being used.
	      /// 
	      protected override void Dispose( bool disposing )
	      {
	         if( disposing )
	         {
	            if (components != null)
	            {
	               components.Dispose();
	            }
	         }
	         base.Dispose( disposing );
	      }

	      #region Windows Form Designer generated code
	      /// 
	      /// Required method for Designer support - do not modify
	      /// the contents of this method with the code editor.
	      /// 
	      private void InitializeComponent()
	      {
	         this.EraseAll = new System.Windows.Forms.Button();
	         this.FillWithHotPink = new System.Windows.Forms.Button();
	         this.AddFilledRectangle = new System.Windows.Forms.Button();
	         this.AddHollowRectangle = new System.Windows.Forms.Button();
	         this.AddPoint = new System.Windows.Forms.Button();
	         this.Drawing = new System.Windows.Forms.PictureBox();
	         this.GroupBox1 = new System.Windows.Forms.GroupBox();
	         this.FilledCircleRadius = new System.Windows.Forms.TextBox();
	         this.Label3 = new System.Windows.Forms.Label();
	         this.FilledCircleY = new System.Windows.Forms.TextBox();
	         this.Label2 = new System.Windows.Forms.Label();
	         this.FilledCircleX = new System.Windows.Forms.TextBox();
	         this.Label1 = new System.Windows.Forms.Label();
	         this.AddFilledCircleAt = new System.Windows.Forms.Button();
	         this.AddHollowCircle = new System.Windows.Forms.Button();
	         this.AddFilledCircle = new System.Windows.Forms.Button();
	         this.GroupBox1.SuspendLayout();
	         this.SuspendLayout();
	         //
	         // EraseAll
	         //
	         this.EraseAll.Location = new System.Drawing.Point(392, 367);
	         this.EraseAll.Name = "EraseAll";
	         this.EraseAll.Size = new System.Drawing.Size(128, 24);
	         this.EraseAll.TabIndex = 20;
	         this.EraseAll.Text = "Erase all";
	         this.EraseAll.Click += new System.EventHandler(this.EraseAll_Click);
	         //
	         // FillWithHotPink
	         //
	         this.FillWithHotPink.Location = new System.Drawing.Point(392, 328);
	         this.FillWithHotPink.Name = "FillWithHotPink";
	         this.FillWithHotPink.Size = new System.Drawing.Size(128, 24);
	         this.FillWithHotPink.TabIndex = 18;
	         this.FillWithHotPink.Text = "Change fills to hot pink";
	         this.FillWithHotPink.Click += new System.EventHandler(this.FillWithHotPink_Click);
	         //
	         // AddFilledRectangle
	         //
	         this.AddFilledRectangle.Location = new System.Drawing.Point(392, 152);
	         this.AddFilledRectangle.Name = "AddFilledRectangle";
	         this.AddFilledRectangle.Size = new System.Drawing.Size(128, 24);
	         this.AddFilledRectangle.TabIndex = 17;
	         this.AddFilledRectangle.Text = "Add Filled Rectangle";
	         this.AddFilledRectangle.Click += new System.EventHandler(this.AddFilledRectangle_Click);
	         //
	         // AddHollowRectangle
	         //
	         this.AddHollowRectangle.Location = new System.Drawing.Point(392, 88);
	         this.AddHollowRectangle.Name = "AddHollowRectangle";
	         this.AddHollowRectangle.Size = new System.Drawing.Size(128, 24);
	         this.AddHollowRectangle.TabIndex = 16;
	         this.AddHollowRectangle.Text = "Add Hollow Rectangle";
	         this.AddHollowRectangle.Click += new System.EventHandler(this.AddHollowRectangle_Click);
	         //
	         // AddPoint
	         //
	         this.AddPoint.Location = new System.Drawing.Point(392, 23);
	         this.AddPoint.Name = "AddPoint";
	         this.AddPoint.Size = new System.Drawing.Size(128, 24);
	         this.AddPoint.TabIndex = 15;
	         this.AddPoint.Text = "Add Point";
	         this.AddPoint.Click += new System.EventHandler(this.AddPoint_Click);
	         //
	         // Drawing
	         //
	         this.Drawing.BackColor = System.Drawing.SystemColors.ControlLightLight;
	         this.Drawing.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
	         this.Drawing.Location = new System.Drawing.Point(8, 15);
	         this.Drawing.Name = "Drawing";
	         this.Drawing.Size = new System.Drawing.Size(368, 376);
	         this.Drawing.TabIndex = 14;
	         this.Drawing.TabStop = false;
	         this.Drawing.Paint += new System.Windows.Forms.PaintEventHandler(this.Drawing_Paint);
	         //
	         // GroupBox1
	         //
	         this.GroupBox1.Controls.AddRange(new System.Windows.Forms.Control[] {
	                                                               this.FilledCircleRadius,
	                                                               this.Label3,
	                                                               this.FilledCircleY,
	                                                               this.Label2,
	                                                               this.FilledCircleX,
	                                                               this.Label1,
	                                                               this.AddFilledCircleAt});
	         this.GroupBox1.Location = new System.Drawing.Point(384, 184);
	         this.GroupBox1.Name = "GroupBox1";
	         this.GroupBox1.Size = new System.Drawing.Size(136, 128);
	         this.GroupBox1.TabIndex = 21;
	         this.GroupBox1.TabStop = false;
	         this.GroupBox1.Text = "Input demo";
	         //
	         // FilledCircleRadius
	         //
	         this.FilledCircleRadius.Location = new System.Drawing.Point(80, 72);
	         this.FilledCircleRadius.Name = "FilledCircleRadius";
	         this.FilledCircleRadius.Size = new System.Drawing.Size(40, 20);
	         this.FilledCircleRadius.TabIndex = 9;
	         this.FilledCircleRadius.Text = "25";
	         //
	         // Label3
	         //
	         this.Label3.Location = new System.Drawing.Point(16, 74);
	         this.Label3.Name = "Label3";
	         this.Label3.Size = new System.Drawing.Size(56, 16);
	         this.Label3.TabIndex = 8;
	         this.Label3.Text = "Radius";
	         this.Label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
	         //
	         // FilledCircleY
	         //
	         this.FilledCircleY.Location = new System.Drawing.Point(80, 48);
	         this.FilledCircleY.Name = "FilledCircleY";
	         this.FilledCircleY.Size = new System.Drawing.Size(40, 20);
	         this.FilledCircleY.TabIndex = 7;
	         this.FilledCircleY.Text = "150";
	         //
	         // Label2
	         //
	         this.Label2.Location = new System.Drawing.Point(16, 50);
	         this.Label2.Name = "Label2";
	         this.Label2.Size = new System.Drawing.Size(56, 16);
	         this.Label2.TabIndex = 6;
	         this.Label2.Text = "Center Y";
	         this.Label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
	         //
	         // FilledCircleX
	         //
	         this.FilledCircleX.Location = new System.Drawing.Point(80, 24);
	         this.FilledCircleX.Name = "FilledCircleX";
	         this.FilledCircleX.Size = new System.Drawing.Size(40, 20);
	         this.FilledCircleX.TabIndex = 5;
	         this.FilledCircleX.Text = "100";
	         //
	         // Label1
	         //
	         this.Label1.Location = new System.Drawing.Point(16, 26);
	         this.Label1.Name = "Label1";
	         this.Label1.Size = new System.Drawing.Size(56, 16);
	         this.Label1.TabIndex = 4;
	         this.Label1.Text = "Center X";
	         this.Label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
	         //
	         // AddFilledCircleAt
	         //
	         this.AddFilledCircleAt.Location = new System.Drawing.Point(8, 96);
	         this.AddFilledCircleAt.Name = "AddFilledCircleAt";
	         this.AddFilledCircleAt.Size = new System.Drawing.Size(120, 24);
	         this.AddFilledCircleAt.TabIndex = 3;
	         this.AddFilledCircleAt.Text = "Add Filled Circle At...";
	         this.AddFilledCircleAt.Click += new System.EventHandler(this.AddFilledCircleAt_Click);
	         //
	         // AddHollowCircle
	         //
	         this.AddHollowCircle.Location = new System.Drawing.Point(392, 55);
	         this.AddHollowCircle.Name = "AddHollowCircle";
	         this.AddHollowCircle.Size = new System.Drawing.Size(128, 24);
	         this.AddHollowCircle.TabIndex = 19;
	         this.AddHollowCircle.Text = "Add Hollow Circle";
	         this.AddHollowCircle.Click += new System.EventHandler(this.AddHollowCircle_Click);
	         //
	         // AddFilledCircle
	         //
	         this.AddFilledCircle.Location = new System.Drawing.Point(392, 120);
	         this.AddFilledCircle.Name = "AddFilledCircle";
	         this.AddFilledCircle.Size = new System.Drawing.Size(128, 24);
	         this.AddFilledCircle.TabIndex = 22;
	         this.AddFilledCircle.Text = "Add Filled Circle";
	         this.AddFilledCircle.Click += new System.EventHandler(this.AddFilledCircle_Click);
	         //
	         // MainWindow
	         //
	         this.Controls.AddRange(new System.Windows.Forms.Control[] {
	                                                        this.AddFilledCircle,
	                                                        this.EraseAll,
	                                                        this.FillWithHotPink,
	                                                        this.AddFilledRectangle,
	                                                        this.AddHollowRectangle,
	                                                        this.AddPoint,
	                                                        this.Drawing,
	                                                        this.GroupBox1,
	                                                        this.AddHollowCircle});
	         this.Name = "MainWindow";
	         this.Text = "The Canonical Polymorphism Demo as a Windows Forms app in C#";
	         this.GroupBox1.ResumeLayout(false);
	         this.ResumeLayout(false);

	      }
	      #endregion

	      /// 
	      /// The main entry point for the application.
	      /// 
	      [STAThread]
	      static void Main() {
	         Application.Run(new MainWindow());
	      }

	      DShapeList drawingList = new DShapeList();
	      Random randomGen = new Random();

	      private Point GetRandomPoint() {
	         return new Point(randomGen.Next(30, 320), randomGen.Next(30, 320));
	      }

	      private void Drawing_Paint(object sender,
	            System.Windows.Forms.PaintEventArgs e) {
	         drawingList.DrawList(e.Graphics);
	      }

	      private void AddPoint_Click(object sender, System.EventArgs e)   {
	          drawingList.Add(new DPoint(GetRandomPoint(), Color.Blue));
	          Drawing.Invalidate();
	      }

	      private void AddHollowCircle_Click(object sender, System.EventArgs e) {
	         drawingList.Add(new DHollowCircle(GetRandomPoint(), 30, Color.Chocolate));
	         Drawing.Invalidate();
	      }

	      private void AddHollowRectangle_Click(object sender, System.EventArgs e) {
	           drawingList.Add(new DHollowRectangle(new Rectangle(
	            GetRandomPoint(), new Size(20, 30)), Color.Green));
	           Drawing.Invalidate();
	      }

	      private void AddFilledCircle_Click(object sender, System.EventArgs e) {
	         drawingList.Add(new DFilledCircle(GetRandomPoint(), 20, Color.Red, Color.LimeGreen));
	         Drawing.Invalidate();
	      }

	      private void AddFilledRectangle_Click(object sender, System.EventArgs e) {
	           drawingList.Add(new DFilledRectangle(
	            new Rectangle(GetRandomPoint(), new Size(40, 50)), Color.Blue, Color.Cyan));
	           Drawing.Invalidate();
	      }

	      private void AddFilledCircleAt_Click(object sender, System.EventArgs e) {
	           drawingList.Add(new DFilledCircle(
	            new Point(Convert.ToInt32(FilledCircleX.Text), Convert.ToInt32(FilledCircleY.Text)),
	            Convert.ToInt32(FilledCircleRadius.Text), Color.Green, Color.CornflowerBlue));
	           Drawing.Invalidate();
	      }

	      private void FillWithHotPink_Click(object sender, System.EventArgs e) {
	           IFillable [] filledList = drawingList.GetFilledList();
	           foreach (IFillable i in filledList)
	            i.FillBrushColor = Color.HotPink;
	           Drawing.Invalidate();
	      }

	      private void EraseAll_Click(object sender, System.EventArgs e) {
	           drawingList = new DShapeList();
	           Drawing.Invalidate();
	      }
	   }
	}

	

	using System;
	using System.Drawing;
	using System.Collections;

	public abstract class DShape {
	   public abstract void Draw(Graphics g);
	   protected Rectangle bounding;
	   protected Color penColor; // should have property, too
	   // should also have methods to move, resize, etc.
	}

	public interface IFillable {
	   void Fill(Graphics g);
	   Color FillBrushColor { get; set; }
	}

	public class DPoint : DShape {
	   public DPoint(Point p, Color penColor) {
		  bounding = new Rectangle(p, new Size(1, 1));
		  this.penColor = penColor;
	   }

	   public override void Draw(Graphics g) {
		  using (Pen p = new Pen(penColor)) {
			 g.DrawRectangle(p, bounding);
		  }
	   }
	}

	public class DHollowCircle : DShape
	{
	   public DHollowCircle(Point p, int radius, Color penColor) {
		  p.Offset(-radius, -radius); // need to convert to upper left
		  int diameter = radius * 2;
		  bounding = new Rectangle(p, new Size(diameter, diameter));
		  this.penColor = penColor;
	   }

	   public override void Draw(Graphics g) {
		  using (Pen p = new Pen(penColor)) {
			 g.DrawEllipse(p, bounding);
		  }
	   }
	}

	public class DFilledCircle : DHollowCircle, IFillable
	{
	   public DFilledCircle(Point center, int radius, Color penColor, Color brushColor)
			 : base(center, radius, penColor) {
		  this.brushColor = brushColor;
	   }

	   public void Fill(Graphics g) {
		  using (Brush b = new SolidBrush(brushColor)) {
			 g.FillEllipse(b, bounding);
		  }
	   }

	   protected Color brushColor;
	   public Color FillBrushColor {
		  get {
			 return brushColor;
		  }
		  set {
			 brushColor = value;
		  }
	   }

	   public override void Draw(Graphics g) {
		  Fill(g);
		  base.Draw(g);
	   }
	}

	public class DHollowRectangle : DShape {
	   public DHollowRectangle(Rectangle rect, Color penColor) {
		  bounding = rect;
		  this.penColor = penColor;
	   }

	   public override void Draw(Graphics g) {
		  using (Pen p = new Pen(penColor)) {
			 g.DrawRectangle(p, bounding);
		  }
	   }
	}

	public class DFilledRectangle : DHollowRectangle, IFillable {
	   public DFilledRectangle(Rectangle rect, Color penColor,
			 Color brushColor) : base(rect, penColor) {
		  this.brushColor = brushColor;
	   }

	   public void Fill(Graphics g) {
		  using (Brush b = new SolidBrush(brushColor)) {
			 g.FillRectangle(b, bounding);
		  }
	   }

	   protected Color brushColor;
	   public Color FillBrushColor {
		  get {
			 return brushColor;
		  }
		  set {
			 brushColor = value;
		  }
	   }

	   public override void Draw(Graphics g) {
		  Fill(g);
		  base.Draw(g);
	   }
	}

	public class DShapeList {
	   ArrayList wholeList = new ArrayList();
	   ArrayList filledList = new ArrayList();

	   public void Add(DShape d) {
		  wholeList.Add(d);
		  if (d is IFillable)
			 filledList.Add(d);
	   }

	   public void DrawList(Graphics g) {
		  if (wholeList.Count == 0)
		  {
			 Font f = new Font("Arial", 10);
			 g.DrawString("Nothing to draw; list is empty...",
				f, Brushes.Gray, 50, 50);
		  }
		  else
		  {
			 foreach (DShape d in wholeList)
				d.Draw(g);
		  }
	   }

	   public IFillable[] GetFilledList() {
		  return (IFillable[])filledList.ToArray(typeof(IFillable));
	   }
	}
	

Exercise: Develop a game of Hangman using Windows Forms.

Assemblies and Modules

See page 479, Programming C#, 3rd Edition.

Assemblies

Assemblies are Portable Executable (PE) files. They can either be implemented as EXE or DLL files. Assemblies can consit of multiple modules, though for simple projects, just one module is included with the assembly itself. Modules are created as DLLs and cannot be executed on their own; they have to part of an assembly. Assemblies are deployed, used, and versioned as a unit. The manifest contains the assembly meta-data, including the name and version of the assembly, a list of types and resources in the assembly, and a map that connects the types in the assembly to the implementing code.

You can use ILDASM.EXE to view assembly metadata.

Multi-Module Assemblies

Multi-module assemblies consist at least one DLL or EXE file. The assembly manifest can reside in a separate assembly or can be incorporate into one of the modules. The benefits of multi-module assemblies is that they can be developed/deployed independently. Modules within an assembly can also be loaded independently.

Exercise: Create a multi-module assembly as per p.482 of Programming C#, 3rd Edition.

Here is the makefile for this project (use nmake /fmakefile.mk /A to compile):

	ASSEMBLY=MultiModuleAssembly.dll

	BIN=.\bin
	SRC=.
	DEST=.\bin

	CSC=csc /nologo /debug+ /d:DEBUG /d:TRACE

	MODULETARGET=/t:module
	LIBTARGET=/t:library
	EXETARGET=/t:exe

	REFERENCES=System.dll

	MODULES=$(DEST)\Fraction.dll $(DEST)\Calc.dll
	METADATA=$(SRC)\AssemblyInfo.cs

	all:$(DEST)\MultiModuleAssembly.dll

	$(DEST)\$(ASSEMBLY): $(METADATA) $(MODULES) $(DEST)
		$(CSC) $(LIBTARGET) /addmodule:$(MODULES: =;) /out:$@ %s


	$(DEST)\Calc.dll: Calc.cs $(DEST)
		$(CSC) $(MODULETARGET) /r:$(REFERENCES: =;) /out:$@ %s

	$(DEST)\Fraction.dll: Fraction.cs $(BIN)
		$(CSC) $(MODULETARGET) /r:$(REFERENCES: =;) /out:$@ %s

	$(DEST)::
	!if !EXISTS($(BIN))
		mkdir $(BIN)
	!endif
	

Public and Private Assemblies

The example above shows how to use a private assembly. A private assembly must be stored along with its application in the same directory tree. No other applications can access such an assembly. If an assembly is going to be used by multiple application, you need to create a public assembly:

Application Deployment

See page 349 of Programming C#, 3rd Edition. There are a number of options for deploying an application: Cab Project, Merge Module Project, Setup Project, Setup Wizard, Remote Deploy Wizard, and Web Setup Project.

Legacy Code

See chapter page 641, Programming C#, 3rd Edition.

Importing ActiveX Controls

Once you have a registered OCX control (use Regsvr32 to register the control, e.g. Regsvr32 CalcControl.ocx), you can import the control into a .NET project. In Visual Studio, choose Tools, Customize Toolbox. On the COM Components tab, find the ActiveX control (e.g. CalcControl). Alternatively, you can generate the assembly for the control using the AXIMP tool, e.g. aximp CalcControl.ocx.

Once this is done, you can return to Customize Toolbox, select .NET Framework Components, and import the AxCalcControl.dll file. You can now drag this control onto your form and use it just as you would a normal ActiveX control.

Importing COM Components

If you have a COM type library for your COM component, you can use early binding using TLBIMP, e.g. tlbimp ComCalculator.dll /out:ComCalculatorDOTNET.dll. You can now use the control, e.g.
	ComCalculator calc = new ComCalculator();
	calc.Add(x, y);

If the type library is not available, you must use late binding with reflection, e.g.

	Type calcType = Type.GetTypeFromProgId(ComCalculator");
	object calcObject = Activator.CreateInstance(calcType);

	Double result = (Double) calcType.InvokeMember("Add", BindingFlags.InvokeMethod,
			null, calcObject, new object[] {(Double)2.2, (Double)3.3});

Exporting .NET components

Once you have a public assembly, you can use the REGASM tool to export the component, e.g. regasm MyAssembly.dll. You can now use this component in VBScript, e.g.
	dim calc
	dim msg
	dim result

	set calc = CreateObject("MyAssemblyNamespace.Calculator");
	result = calc.Multiply(7, 3);
	msg = "7 * 3 = : & result & ".";
	Call MsgBox(msg);

Creating a Type Library

If you wish to use early binding, you can create a type library using TLBEXP, e.g. tlbexp MyAssembly.dll /out:Calc.tlb

P/Invoke

You can make calls to unmanaged code by declaring a static extern function, e.g.
	[DllImport("kernel32.dll", EntryPoint="MoveFile", ExactSpelling=false, CharSet=Charset.Unicode,
	 SetLastError=true)]
	public static extern bool MoveFile(string sourceFile, string destinationFile);
See page 660 of Programming C#, 3rd Edition for more details.

ADO.NET

See page 359, Programming C#, 3rd Edition for more information. The primary objects in the ADO.NET object model are as follows: There are two kinds of managed providers included with ADO.NET: You can navigate relationships between tables with ADO.NET (page 377, Programming C#, 3rd Edition). Create a DataRelation object, e.g.
	parentColumn = dataSet.Tables["Customer"].Columns["CustomerId"];
	childColumn = dataSet.Tables["Order"].Columns["CustomerId"];

	DataRelation dataRelation = new System.Data.DataRelation("CustomersToOrders",
			parentColumn, childColumn);

    grid1.DataSource = dataSet.DefaultViewManager;
    grid1.DataMember = "Customer";
Exercise: Review example on page 383 of Programming C#, 3rd Edition. Also, create a form using the DataForm Wizard. Explore the code in both cases.

Web Services

See page 421, Programming C#, 3rd Edition for more information. Web Services is a suite of technologies designed to facilitate the deployment of applications as services over the Internet. One of the reasons for the development of the Web Services model is to make it easy for people to make various services available in a standard and generic way to other parties. A company might make services available to other companies, or a department might make services available to other departments. In this respect, Web Services is a model to facilitate enterprise integration. For example, a pipeline management company may have a department that stores the most up-to-date information about gas pool and well locations, production, etc. It may then encapsulate that information in the form of web wervices so that other departments may be able to integrate that information into their own applications. Another aspect of Web Services concerns E-Commerce. In this regard, Web Services can be used as a platform to process micro-payments for per-access use of an application. A company may expose a Web service that yields real time stock information, and any other applications that use this Web Service would incur an automatic debit of some kind each time they request that service.

Creating a Web Service

Creating a Web service in .NET and Visual Studio with Internet Information Server is quite trivial. Simply add the [WebMethod] attribute to any method you wish to expose as a Web Service. A simple way to create a Web Service is to create a new Web Service Project in Visual Studio (you will need to have Internet Information Service running).

Creating a Web Client

Use WSDL to create a proxy for your Web Service client, e.g. wsdl http://localhost/WSCalc/service1.asmx?wsdl. Add the appropriate namespace to the generated code and build a library (DLL) from it. Next, write a simple client using this proxy referencing that DLL. Simply instantiate the client, and away you go. You can access your Web Service directly over the Web also, e.g.

http://localhost/WSCalc/Service1.asmx/Add?x=32&y=22

ASP.NET

See page 401, Programming C#, 3rd Edition. ASP.NET uses Web Forms technology to create Web applications (as opposed to Windows forms, which are used to create Windows applications).

Each Web Form consists of two files, the GUI and the back-end code: The gui which contains all of the controls is stored in a .aspx file. The code-behind file, which handles all of the events is in a normal C# file (using the same name as the form is a reasonable convention).

ASP.NET is event-driven. In other words, when a form is submitted, the server issues events based on the state of the form. For example, if you've selected a particular radio button on the form, the CheckedChanged event will fire for that radio button. Postback events cause the form to actually submit, so asp:Button is an obvious example. asp:RadioButton is not a PostBack object, so the event for button selection will only be fired once the submit button is clicked. One can generally cause a non-postback control to become a postback control by setting AutoPostback=True, e.g.

	<asp:RadioButton runat="server" id="button1"
			 Text="One" GroupName="radioGroup"
			 AutoPostBack="true" />

Appendix A: Hangman Component

Unit tests

	namespace HangmanServer {

	using System;
	using NUnit.Framework;
	using System.Collections;

	[TestFixture]
	public class TestHangman
	{
		[Test] public void CreateGame()
		{
			Hangman hangman = new Hangman("happy");
			Assert.AreEqual(GameState.IN_PROGRESS, hangman.GetGameState());
			Assert.AreEqual("happy", hangman.GetSecretWord());
			Assert.AreEqual(0, hangman.GetErrorCount());

			char[] letters = hangman.GetLetters();
			for (int i=0; i<5; i++) {
				Assert.AreEqual('\0', letters.GetValue(i));
			}
		}

		[Test] public void SecretWordAlwaysLowerCase()
		{
			Hangman hangman = new Hangman("HaPPy");
			Assert.AreEqual("happy", hangman.GetSecretWord());
		}

		[Test] public void CorrectGuess()
		{
			Hangman hangman = new Hangman("happy");
			hangman.Guess('a');

			Assert.AreEqual(GameState.IN_PROGRESS, hangman.GetGameState());
			Assert.AreEqual(0, hangman.GetErrorCount());

			char[] letters = hangman.GetLetters();
			Assert.AreEqual('\0', letters.GetValue(0));
			Assert.AreEqual('a', letters.GetValue(1));
			Assert.AreEqual('\0', letters.GetValue(2));
			Assert.AreEqual('\0', letters.GetValue(3));
			Assert.AreEqual('\0', letters.GetValue(4));
		}

		[Test] public void CorrectGuessUppercase()
		{
			Hangman hangman = new Hangman("happy");
			hangman.Guess('P');

			Assert.AreEqual(GameState.IN_PROGRESS, hangman.GetGameState());
			Assert.AreEqual(0, hangman.GetErrorCount());

			char[] letters = hangman.GetLetters();
			Assert.AreEqual('\0', letters.GetValue(0));
			Assert.AreEqual('\0', letters.GetValue(1));
			Assert.AreEqual('p', letters.GetValue(2));
			Assert.AreEqual('p', letters.GetValue(3));
			Assert.AreEqual('\0', letters.GetValue(4));
		}

		[Test] public void WrongGuess()
		{
			Hangman hangman = new Hangman("happy");
			hangman.Guess('x');

			Assert.AreEqual(GameState.IN_PROGRESS, hangman.GetGameState());
			Assert.AreEqual(1, hangman.GetErrorCount());

			char[] letters = hangman.GetLetters();
			for (int i=0; i<5; i++) {
				Assert.AreEqual('\0', letters.GetValue(i));
			}
		}

		[Test] public void GameLost()
		{
			Hangman hangman = new Hangman("sad");

			hangman.Guess('b');
			Assert.AreEqual(1, hangman.GetErrorCount());
			hangman.Guess('c');
			Assert.AreEqual(2, hangman.GetErrorCount());
			hangman.Guess('d');
			Assert.AreEqual(2, hangman.GetErrorCount());
			hangman.Guess('e');
			Assert.AreEqual(3, hangman.GetErrorCount());
			hangman.Guess('f');
			Assert.AreEqual(4, hangman.GetErrorCount());
			hangman.Guess('g');
			Assert.AreEqual(5, hangman.GetErrorCount());
			hangman.Guess('h');
			Assert.AreEqual(6, hangman.GetErrorCount());
			hangman.Guess('i');
			Assert.AreEqual(7, hangman.GetErrorCount());
			Assert.AreEqual(GameState.LOST, hangman.GetGameState());

			char[] letters = hangman.GetLetters();
			Assert.AreEqual('\0', letters.GetValue(0));
			Assert.AreEqual('\0', letters.GetValue(1));
			Assert.AreEqual('d', letters.GetValue(2));
		}

		[Test] public void GameWon()
		{
			Hangman hangman = new Hangman("happy");

			hangman.Guess('h');
			hangman.Guess('a');
			hangman.Guess('p');
			hangman.Guess('y');

			Assert.AreEqual(0, hangman.GetErrorCount());
			Assert.AreEqual(GameState.WON, hangman.GetGameState());

			char[] letters = hangman.GetLetters();
			Assert.AreEqual('h', letters.GetValue(0));
			Assert.AreEqual('a', letters.GetValue(1));
			Assert.AreEqual('p', letters.GetValue(2));
			Assert.AreEqual('p', letters.GetValue(3));
			Assert.AreEqual('y', letters.GetValue(4));
		}

		[Test] public void RepeatedWrongGuessIsFree()
		{
			Hangman hangman = new Hangman("repeat");

			hangman.Guess('x');
			hangman.Guess('x');
			Assert.AreEqual(1, hangman.GetErrorCount());
		}

		[Test] public void RepeatedCorrectGuessIsFree()
		{
			Hangman hangman = new Hangman("repeat");

			hangman.Guess('e');
			hangman.Guess('e');
			Assert.AreEqual(0, hangman.GetErrorCount());
		}

		[Test] public void SecretWordCannotBeNull()
		{
			try
			{
				Hangman hangman = new Hangman(null);
				Assert.Fail("secret word cannot be null");
			}
			catch (InvalidSecretWordException e)
			{
				Assert.AreEqual("secret word cannot be null", e.Message);
			}
		}

		[Test] public void SecretWordMinimumTwoCharacters()
		{
			try
			{
				Hangman hangman = new Hangman("x");
				Assert.Fail(
						"secret word must be at least two characters long");
			}
			catch (InvalidSecretWordException e)
			{
				Assert.AreEqual(

						"secret word must be at least two characters long",
						e.Message);
			}
		}

		[Test] public void LostGameCannotBeContinued()
		{
			Hangman hangman = new Hangman("sad");

			hangman.Guess('b');
			hangman.Guess('c');
			hangman.Guess('e');
			hangman.Guess('f');
			hangman.Guess('g');
			hangman.Guess('h');
			hangman.Guess('i');

			try
			{
				hangman.Guess('x');
				Assert.Fail("game is over");
			}
			catch (GameOverException)
			{
			}
		}

		[Test] public void WonGameCannotBeContinued()
		{
			Hangman hangman = new Hangman("happy");

			hangman.Guess('h');
			hangman.Guess('a');
			hangman.Guess('p');
			hangman.Guess('y');

			try
			{
				hangman.Guess('x');
				Assert.Fail("game is over");
			}
			catch (GameOverException)
			{
			}
		}

		[Test] public void GuessHistoryIsTracked()
		{
			Hangman hangman = new Hangman("repeat");

			hangman.Guess('r');
			hangman.Guess('x');
			hangman.Guess('r');
			hangman.Guess('t');

			IEnumerator guessHistory = hangman.GetGuessHistory();

			guessHistory.MoveNext();
			GameGuess gameGuess = (GameGuess) guessHistory.Current;
			Assert.AreEqual('r', gameGuess.Letter);
			Assert.AreEqual(GameGuess.CORRECT, gameGuess.Status);

			guessHistory.MoveNext();
			gameGuess = (GameGuess) guessHistory.Current;
			Assert.AreEqual('x', gameGuess.Letter);
			Assert.AreEqual(GameGuess.WRONG, gameGuess.Status);

			guessHistory.MoveNext();
			gameGuess = (GameGuess) guessHistory.Current;
			Assert.AreEqual('r', gameGuess.Letter);
			Assert.AreEqual(GameGuess.REPEAT, gameGuess.Status);

			guessHistory.MoveNext();
			gameGuess = (GameGuess) guessHistory.Current;
			Assert.AreEqual('t', gameGuess.Letter);
			Assert.AreEqual(GameGuess.CORRECT, gameGuess.Status);
	}

		/* note: should make a test to make sure secret
		 * word is a dictionary word */
	}

	} //end namespace
	

Hangman Component

	namespace HangmanServer
	{

	using System;
	using System.Collections;

	public class Hangman
	{
		private int errorCount;
		private string secretWord;
		private char[] letters;
		private ArrayList previousGuesses = new ArrayList();
		private GameState gameState = GameState.IN_PROGRESS;

		public static void Main(string[] args)
		{
		}

		public Hangman(string secretWord)
		{
			ValidateSecretWord(secretWord);

			this.secretWord = secretWord.ToLower();
			letters = new char[secretWord.Length];
		}

		public GameState GetGameState()
		{
			return gameState;
		}

		public int GetErrorCount()
		{
			return errorCount;
		}

		public char[] GetLetterMask()
		{
			return letters;
		}


		public void Guess(char guess)
		{
			CheckGameOver();

			GameGuess guessHistoryItem;
			if (IsRepeatGuess(guess))
			{
				guessHistoryItem = new GameGuess(guess, GameGuess.REPEAT);
				previousGuesses.Add(guessHistoryItem);
				return;
			}

			bool correctGuess = false;
			for (int i=0; i<secretWord.Length; i++)
			{
				char letter = secretWord[i];
				if (guess == letter ||
						char.ToLower(guess) == letter)
				{
					letters[i] = letter;
					correctGuess = true;
				}
			}

			if (correctGuess == false)
			{
				guessHistoryItem = new GameGuess(guess, GameGuess.WRONG);
				errorCount++;
			} else
			{
				guessHistoryItem = new GameGuess(guess, GameGuess.CORRECT);
			}

			previousGuesses.Add(guessHistoryItem);

			CheckGameOver();
		}

		public IEnumerator GetGuessHistory()
		{
			IEnumerable a;
			return previousGuesses.GetEnumerator();
		}

		internal string GetSecretWord()
		{
			return secretWord;
		}

		private bool IsSecretSolved()
		{
			for (int i=0; i<letters.Length; i++)
			{
				if (letters[i] != secretWord[i])
				{
					return false;
				}
			}

			return true;
		}

		private void CheckGameOver()
		{
			if (gameState == GameState.WON
						|| gameState == GameState.LOST)
			{
				throw new GameOverException();
			}

			if (errorCount == 7)
			{
				gameState = GameState.LOST;
			}

			bool solved =  IsSecretSolved();
			if (solved)
			{
				gameState = GameState.WON;
			}
		}

		private bool IsRepeatGuess(char guess)
		{
			foreach (GameGuess previouslyGuessedLetter
					in previousGuesses)
			{
				if (previouslyGuessedLetter.Letter == guess)
				{
					return true;
				}
			}

			return false;
		}

		private void ValidateSecretWord(String secretWord)
		{
			if (secretWord == null)
			{
				throw new InvalidSecretWordException(
						"secret word cannot be null");
			}

			if (secretWord.Length < 2)
			{
				throw new InvalidSecretWordException(
						"secret word must be at least two characters long");
			}
		}
	}

	} //end namespace
	

GameGuess

	namespace HangmanServer {

	public struct GameGuess
	{
		public const string CORRECT = "CORRECT";
		public const string WRONG = "WRONG";
		public const string REPEAT = "REPEAT";

		private char letter;
		private string status;

		public GameGuess(char letter, string status)
		{
			this.letter = letter;
			this.status = status;
		}

		public char Letter
		{
			get
			{
				return letter;
			}
		}

		public string Status
		{
			get
			{
				return status;
			}
		}
	}

	} //end namespace
	

GameState

	namespace HangmanServer
	{

	public enum GameState
	{
		IN_PROGRESS,
		WON,
		LOST
	}

	} //end namespace
	

GameOverException

	namespace HangmanServer
	{

	public class GameOverException : System.ApplicationException
	{
		public GameOverException ()
		{
		}
	}

	} //end namespace

	

InvalidSecretWordException

	namespace HangmanServer
	{

	public class InvalidSecretWordException : System.ApplicationException
	{
		public InvalidSecretWordException (string message) : base(message)
		{
		}
	}

	} //end namespace