Description:  This document is a tutorial in a series of tutorials for programmers learning about the .NET Framework development environment.  What you will learn is what C# is, as well as how to be a successful object oriented programmer with C#.

Requirements:  You should be familiar with at least one programming language, such as C++, Pascal, PERL, Java or Visual Basic.  You should have some comfort with object oriented concepts such as instantiating and using objects.  You should be comfortable with general computer science concepts.  To do the exercises and run the examples you need a PC running Windows with the .NET Framework installed.

Table of Contents

Table of Contents. 1

Figures and Exercises. 3

1      Writing Object Oriented Software with C#. 4

2      Object Oriented Concepts (A Refresher) 4

2.1       Types and Instances. 5

2.2       Type Derivation. 6

2.3       References to Instances. 7

2.4       Polymorphism.. 8

2.5       Members and Basic Accessibility. 8

3      Designing Types. 9

3.1       Fields. 10

3.2       Methods. 11

3.3       Properties. 12

3.4       Constructors. 13

3.5       Type Constructors. 14

3.6       Accessibility. 14

3.7       Derivation. 15

3.8       Polymorphism.. 16

4      Event Handling. 18

4.1       Handling Events. 18

4.2       Defining Events. 19

4.3       Callback Methods. 20

5      Interfaces. 21

6      Operator Overloading and Type Conversion. 22

6.1       Operator Overloading. 22

6.2       Casting Operators (type converters) 23

7      OOP in Practice. 24


Figures

Figure 2‑1  Types and Instances. 6

Figure 2‑2  Derivation. 7

Figure 3‑1  Point.cs Property Example. 12

Figure 3‑2  Instance Constructors. 13

Figure 3‑3  Accessibility Modifiers. 15

Figure 3‑4  Derived Type. 15

Figure 3‑5  Polymorphism.cs. 17

Figure 4‑1  EventHand.cs. 18

Figure 4‑2  EventInt.cs. 19

Figure 4‑3  EventInt consumer 20

Figure 4‑4  Delegates.cs. 20

Figure 5‑1  Sortable.cs. 21

Figure 6‑1  Overloading.cs. 22

Figure 6‑2  Type Converters. 23


Writing Object Oriented Software with C#

The .NET Framework is a new platform designed to target the Internet.  The .NET framework is also an object-oriented platform from the ground up.  And you most likely already know that C# is one of the most popular language choices for programming .NET or managed software.

In the last tutorial in this series I introduced C# with some code samples, a discussion of syntax, and a briefer on how to get your C# programs written and built.  In this tutorial, we will take this information a step further and pursue C# as a best-of-breed object oriented programming language.

I love the ideas behind object oriented programming.  The concepts are sound, and from time to time the implementations are just as sound.  But the fact remains that many mainstream programming languages are less than pure in their approach to OO.  C# is not one of these languages.

First things first, the API (application programmer interface) that C# uses to do stuff is called the Framework Class Library.  The Framework Class Library is a huge library of objects meant for use in your managed code.  C# and managed code in general really has only one way to gain access to underlying features of its host OS and that is through objects.

Second, C# is a first class citizen in the discipline of creating objects.  C# comes complete with the basics, such as the ability to create classes of objects, abstract data, implement polymorphism and the like.  C# extends these abilities with some nice bells and whistles such as properties, operator overloading, and built in event-handling constructs.

Third, the marriage of points one and two are surprisingly successful.  The objects in the FCL are designed to be extended, and C# is a great language to use to make these derivations.  The more I develop in C#, the more I notice that my applications are largely made of objects that extend objects in the FCL; meanwhile my application-only code is becoming increasingly terse.  In fact much of my code fits-in so well with the objects in the FCL that they are hard to distinguish.  This is a good progression and leads to more flexible software in general.

The remainder of this tutorial is almost completely dedicated to describing the features of C# as it pertains to object oriented programming.  I am not going to attempt any major discussion on approach or design.  But I do want to take a quick side-trip through some OO terms just to make sure that our terminology is in sync.

1      Object Oriented Concepts (A Refresher)

The basic goals of Object Oriented (OO) programming are simple.  One is to hide complexity by abstracting data behind a wall of methods or functionality.  The second (and this is related) is to group data with the code that manipulates the data.  The third is modularity; the ability plug-in and un-plug code modules is very important. 

One means of reaching these goals is the creation of classes of objects.  These classes of objects are often called classes, but for the remainder of this tutorial I am going to refer to classes of objects as types, and then when I address specific varieties of types I will use specific terms like class, structure, or enumerated type.

Another means of reaching the OO goals is through type derivation and polymorphism.  These features help you as a programmer to realize the goal of code modularity.

The basic ideas are simple, but the implementations vary a lot, and so there is much to learn.

1.1         Types and Instances

As an object oriented programmer you will first and foremost create instances of types.  This means that you will use a definition for a type, which has a name such as String or ArrayList, to create an actual object in memory.  This object is structured based on the details described in the type’s definition.

After you have created an object, you can use it by calling methods and/or referencing fields on the object.  When you are finished with the object it must be cleaned up.  Is some environments you do this explicitly; in others, such as C# or Java, cleanup is done for you by the system.

Creating instances is a nice introduction to OO programming, but eventually you will have to define your own type.  By doing so you create a new classification for a kind of object that can be created.  You give the type a name, and you create members of the type such as methods and fields.

It is important to distinguish between types and instances, so I will make an analogy.  Think of the type as a descriptive tool (like a cookie cutter), while an instance is an object created from that description (in the same way that a cookie is created from a cookie cutter).

Figure 11  Types and Instances

Finally, the process of using a type to create an object is called instantiation.  In C#, C++, and Java the new keyword is used to instantiate an object.  Sometimes I will use the conversational term, “new-up” to indicate an instantiation.

1.2         Type Derivation

Most object oriented languages allow you to derive a type from another existing type.  (In fact C# and Java both require a type to be derived from a base type).  When a type is derived from another type it becomes an extension of the base type.  As an extension it inherits with it all of the features and functionality of the base type, and most likely has some new features added.

If type Automobile is derived from type Machine, then type Machine is the base class or base type in the relationship.  Conversely, type Automobile would be the derived type, or derived class.  Because of derivation, types exist in a logical derivation hierarchy. 

Figure 12  Derivation

1.3         References to Instances

From the computer’s point of view, objects are data.  They are the culmination of their fields and enough information to indicate their type.  Often this data is complex and sizeable, and it is stored in the memory heap of the program that created the instance.

Because objects so often live in the heap-memory of a program, the most common way of dealing with an instance is through a reference variable.  The reference variable can be a global or local variable, or it can be a field in another object.  Either way, there are some rules of reference variables.

Reference variables have a type associated with them.  For every object-type defined in an object oriented system, there is a matching reference variable type that is used to refer to instances of the type.

Reference variables can refer to an instance or object, or they can refer to null (in most OO languages anyway).  A null reference simply indicates that this variable does not refer to any object, but could have an object reference assigned to it.

Reference variables do not always refer to objects of the exact same type as the reference variable.  This can be confusing, but in fact the rules are simple.  A reference variable must refer to an object of matching type or it must refer to an object that is ultimately derived from the matching type.

Looking back to the relationship between Automobile and Machine, a reference variable of type Automobile can only refer to an instance of Automobile; however a reference variable of type Machine can refer to an instance of Machine or an instance of Automobile.  You can look at this as being possible, because Automobile is a Machine through the affect of derivation.

Reference variables are your means of access to an object or instance.  What this means is that you use the reference variable to touch fields of the object and you use the reference variable to call methods on the object.  There are two related rules of reference variables that make the most sense when stated together.

·          Regardless of what type of instance your reference variable refers to, it is the type of the variable that constrains how you can touch or affect the instance. 

·          Regardless of what type of reference variable you are using to refer to an object, the type of the object never changes for the life of the object.

1.4         Polymorphism

Polymorphism is closely related to type-derivation and reference variables.  But it is an advanced topic, and can be difficult to describe.  The goal is to allow a generic piece of code to work with objects or instances generically; meanwhile we want the objects themselves to do the right thing based on their respective types.

An example of this would be a piece of code that iterates over an array of references to Automobile objects.  The twist, however, come in the fact that the various instances are actually derived types such as Car, Motorcycle, and Bus.

If part of the algorithm was to call an instance method called GetNumWheels(), you would want to be sure that the GetNumWheels() method for a Car is called when the instance is a Car, and the GetNumWheels() method for a Motorcycle is called when the instance is a Motorcycle.

This is not what happens by default, however, since by default the system sees your code calling the GetNumWheels() method when your reference variable is an Automobile reference variable.  The natural flow of code would be to call the code for GetNumWheels() implemented by the Automobile type.

To get the polymorphic behavior that we want we must mark the GetNumWheels() method as virtual on the base class, and all of the derived classes must override the virtual function.

1.5         Members and Basic Accessibility

When defining a type you will give it a name, and perhaps a base type.  This is the easy part.  You still need to make the type do something.  To do this you must define type members.

Type members come in two basic forms.  These are fields and methods.  Fields are the data that make up an instance of your type.  Methods are the functions defined by your type to manipulate the data (although not all types have fields and not all methods manipulate data).  As you work with OO languages and platforms you will see many variations on the method and field rule, but ultimately types are defined as fields and methods.

Most object oriented languages allow you to define more than one method (for a single type) with the same name.  This is called method overloading, and it is a requirement that the parameters of the methods differ so that the compiler has enough context to know which of the various overloads of a method is being used in a specific piece of code.  The composite of a methods name and parameter list is called a methods signature.

Members must have a defined accessibility.  The accessibility of a member (be it field or method) defines who can access the member.  Here are the basic accessibilities defined by most OO languages.

·          Public – If you hold a reference to an object, you can access all of its public members.

·          Private – Only the code in the member functions of a type can access the private members of that type.  For many OO languages, private is the default accessibility, since the goal of OO is to hide data.

·          Protected – Only the code in the member functions of a type or a type derived from the type can access protected members of the base type.

2      Designing Types

Ok, we have talked a little bit about terms, now it is time to talk about designing types with C#.  There are actually a couple of different kinds of types that you can define with C#, and each has its own special behavior.  The class is the most common type, so we will start with that.

To define a class you use the following syntax.

class Name:BaseType{

}

The keyword class is required.  The Name indicates the name of your class, and the BaseType indicates the base class for your new class.  In C#, or any managed language, all types must have a base class.  If one is not defined, then the base class for your type is implicity the Object class defined in the Framework Class Library (FCL).

Types defined for use only by your application will often exist in the default namespace, which is to say that the type has no namespace.  However, as you design types for reuse, you may want to place the type in a namespace declaration as follows.

Namespace NameName{

   class Name:BaseType{

   }

}

In this case a type named NameName.Name is defined.  You can place more than one type definition inside of a namespace definition, and you can reuse namespaces across code modules and assemblies.

Normally you will want your type to do something (other than what the base type already does), so you will add members.  Members come in the form of fields and methods.

2.1         Fields

Fields in a type are data.  In C# fields come in two flavors, static and instance.  Instance fields are the more common, and are part of the definition of an object when it is created.  Instance fields are object data.

Static fields, on the other hand, are data associated with the type.  In a way you can think of static fields as data that are shared between every instance of a type.

class MyType{

   public static String someTypeState;

   public Int32 x;

   public Int32 y;

}

In the preceding code example, you can see a type named MyType that defines three fields.  Two of the fields are instance fields of type Int32, and the third is a static field of type String.

If you instantiate an instance of MyType, the new instance gets its own copy of the x and y fields.  However, as each new instance of MyType gets its own instance fields, there remains only one field named someTypeState which is shared globally amongst each instance.

Notice that each field in the example is explicitly marked as public.  This means that all code has access to the fields of MyType.  If the fields had been marked as private, then only methods of MyType (of which there are none) could directly access the fields in the class.  This is how that definition would look.

class MyType{

   private static String someTypeState;

   private Int32 x;

   private Int32 y;

}

And since the private accessibility is the default in C#, the following definition compiles to the exact same binary as the preceding code.

class MyType{

   static String someTypeState;

   Int32 x;

   Int32 y;

}

Note:  The .NET Framework also has a concept of readonly fields which can only be written to by code inside of a constructor method.

2.2         Methods

In object oriented programming, it is very common for all fields of a type to be private, and for the only access to a type to be obtained through public or protected method calls.

Like fields, methods come in two flavors, static and instance.  Instance methods refer to an object, and must be called through a reference variable that refers to an object instance.  Static methods are often called type-methods and can be called without an instance of the type.  The syntax for calling a static method is.

TypeName.MethodName();

The following type definition shows how to define a class with several methods and some fields.

class Point{

   public Int32 GetX(){

      return x;

   }

 

   public Int32 GetY(){

      return y;

   }

 

   Int32 x;

   Int32 y;

 

   public static Point GetMousePoint(){

      Point pt = new Point();

      // ... code to get mouse coords

      pt.x = Mouse.x;

      pt.y = Mouse.y;

      return pt;

   }

}

In the preceding example two instance methods GetX() and GetY() are defined as accessor methods for the private fields x and y.  Finally, a static method is defined, and in this case, the static method returns an instance of the type (this is a common design pattern called a factory method).

In C# programming methods are used to access data in instances of types.  Methods are also used just to get an application task done.  It is common in C# to define types with nothing but static methods (and no data), just as a logical collection of methods that share a common idea or goal, if not common data.  (The Console type defined by the FCL is an example of such a type).

2.3         Properties

Properties are a great addition to object oriented languages.  Properties are often called smart fields, because the can be accessed using field access syntax, but under the covers they are really method calls.

The advantage of properties is simply that your actual data fields can remain private, while code that consumes your types can consume the fields through properties.  This allows your type to validate data, or do whatever needs to be done in the method portion of the property.

A property has a name and a type, and comes with a read function and a write function named get and set respectively.

using System;

 

class Point{

   Int32 x;

   Int32 y;

 

   public Int32 X{

      get{return x;}

      set{x = value;}

   }

 

   public Int32 Y{

      get{return y;}

      set{y = value;}

   }

}

 

class App{

   public static void Main(){

      Point p = new  Point();

      p.X = 10;

      p.Y = p.X+5;

      Console.WriteLine(p.Y);

   }

}

Figure 21  Point.cs Property Example

In Figure 21 the simple Point class uses properties to elegantly allow access to the private fields x and y. 

Note: The get and set methods are like any other method, and anything can be done in them (however, since these ones are so short, I implemented them on a single line).

Notice that the code in Main() method creates an instance of Point, and then calls both the get and the set property methods.  However, it looks like a simple field access.

Note: It is possible to define read-only and write-only properties by just implementing one or the other of the accessor methods.  It is not valid to define a property with no accessor methods.

You should use a property wherever you are tempted to define a public field.  You should do this even if you are doing what I am doing in the Point.cs Property Example.  The reason is that you may some-day want to add validation.  Or you may want to add an event that is fired when a value is changed.  If you are using a public field, then you can not do either of these things, but with properties you can. 

If you change a member field to a property, all code that references your type will have to be compiled.

2.4         Constructors

When an object is created or instantiated, the .NET Framework allocates memory for the fields of the object, as well as for any overhead data necessary to maintain the object’s type identity.  Although the Common Language Runtime automatically initializes the overhead data, the field data’s initial values must be set.

To do this, you can define a special method for your type called an instance constructor.  The constructor method is called by the runtime when an instance is created.  It is possible for constructors to be overloaded so that an instance of your type can be created with a variety of starting data.

The C# compiler knows you are defining a constructor method when you give a method the exact same name as the type itself.  For example, if we were to add a constructor for the Point type in Figure 21 we would name the constructor Point. 

Inside of your constructor methods you should initialize the fields of your type.

class Point{

   Int32 x;

   Int32 y;

 

   public Point():this(0, 0){}

   public Point(Int32 x, Int32 y){

      this.x = x;

      this.y = y;

   }

 

   public Int32 X{

      get{return x;}

      set{x = value;}

   }

 

   public Int32 Y{

      get{return y;}

      set{y = value;}

   }

}

Figure 22  Instance Constructors

In this update of the Point type, there are two constructors that can be used to new-up an instance of Point.  The first constructor takes no parameters.  A destructor that takes no parameters is called the default constructor.  In this example the default constructor is actually implemented in-terms-of the more complex overload of the constructor which expects two parameters.

It is not necessary for constructors to be implemented in terms of each other, but it is strongly recommended, so that your initialization code need only be written once.

You could create an instance of the Point type in Figure 22 with either of the two following lines code.

Point p = new Point();

Point p = new Point(10, 10);

In the first case you would get an instance of Point with its x and y value initialized to 0, and in the second case you would get a Point object with its x and y fields initialized to 10.

You should try to keep your instance constructor code is minimal as possible, and generally it should relate only to the setting of fields in an instance.

Constructors can be marked with any accessibility modifier, including private.  A private constructor will cause your type’s methods to be the only code that can new up an instance of it.  This is a common design pattern for types where a static factory method is used to create instances.

2.5         Type Constructors

With C# you can create a constructor method designed to initialize a type, rather than to initialize an instance of a type.  This can be useful for setting the values of static fields in your type.

Type constructors are often called static constructors, because you use the static keyword to indicate that a constructor method is a type constructor.  Type constructors must also be named for the type, must take zero parameters, and cannot be overloaded.

The system guarantees that a type constructor for a type will be called before any other reference to a type or an instance of a type is reached.  However, C# does not guarantee that a type constructor will be called at a deterministic time in your program’s execution.

2.6         Accessibility

Most object oriented languages provide accessibility modifiers for members such as public, private, and protected.  The .NET Framework has support for several others, and so does C#.

Accessibility Modifier

Description

public

All code can access this member or type.

private

Only methods in the containing type can access this member.

protected

Only methods in the containing type or in a derived type can access this member.

internal

Methods in the same assembly can access this member or type.

protected internal

This member is accessible by code that would have had access to a protected type, and it is accessible to code that would have had access to an internal type.

Figure 23  Accessibility Modifiers

Both the internal and protected internal accessibility modifiers are advanced and less commonly used with members (although the default accessibility for a type is internal).  For an introduction to assemblies see the tutorial named Introducing the .NET Framework with C#.

2.7         Derivation

We have already talked about derivation, and we will talk about it quite a bit more in the section on Polymorphism.  Here I would just like to show a complete example of derivation.

class NamedPoint:Point{

   String name;

 

   public NamedPoint(String name):this(name, 0, 0){}

   public NamedPoint(String name, Int32 x, Int32 y):base(x, y){

      this.name = name;

   }

 

   public String Name{

      get{return name;}

   }

}

Figure 24  Derived Type

The code in Figure 24 shows a type NamedPoint that is derived from the Point type defined in Figure 22.  Even though the NamedPoint type defines only one field name and one property Name, an instance of NamedPoint will contain all three fields name, x, and y which is the composite of the NamedPoint and Point types. 

Not only is it not necessary for NamedPoint to define its own fields x and y, but an instance of NamedPoint is both of type NamedPoint and of type Point, so a reference variable of either type can be used to reference an object of type NamedPoint.

Note: Because all types are ultimately derived from the Object type, reference variables of type Object are often used as generic references in managed code.  The Object type also defines a method named GetType(), which is inherited by every managed object, and it can be called to obtain the type of an instance.

The constructor syntax for NamedPoint is also noteworthy.  First, notice that the simpler constructor is implemented in terms of the more complex constructor.  This is consistent with the code in Figure 22.  Second, notice that the implementing constructor indicates which version of the base-types constructor should be used to construct the fields that are part of the base type. 

2.8         Polymorphism

In C# polymorphism is implemented through the use of virtual functions in much the same manner that it is with C++.  The syntax is a little different however.

When defining a type, if you are creating a method for which you want polymorphic behavior, then you should use the virtual keyword to decorate the method as follows.

In the derived type, if you chose to overload the virtual function you should create a method with the exact same name and signature and decorate it with the override keyword.

using System;

 

class App{

   public static void Main(String[] args){

      Object[] objects = new Object[]{

            new Lemon(), new Grapefruit(), new Truck(),

            new Lemon(), new Lime()

         };

     

      foreach(Object o in objects){

         Console.WriteLine(o.ToString());

      }

   }

}

 

class Citrus{

   public override String ToString(){

      return "I am a "+this.GetType().ToString();

   }

}

 

class Lime:Citrus{

}

 

class Lemon:Citrus{

}

 

class Grapefruit:Citrus{

}

 

class Truck{

   public override String ToString(){

      return "Truck here";

   }

}

Figure 25  Polymorphism.cs

The code in Figure 25 is a fairly complete example of derivation at work, including a healthy dose of polymorphism.  The first two lines of the Main() method create an array of Object references, and then assigns references to five new objects to the array. 

The line of code in red is the code that is calling an instance method on each object in the array.  This instance method is the ToString() method defined by the Object type in the FCL.  The ToString() method is virtual and will exhibit polymorphic behavior.

If you compile and run the code in Figure 25 you will see that each instance prints its own name to the console window, because the appropriate version of ToString() is called on a per-instance bases.

Note: Four of five of the instances in this example are derived from the Citrus type.  But one of the objects, the instance of Truck, is not.  This is why our array must be an array of Object references rather than an array of Citrus references.  If we remove the Truck object from the set, then we can upgrade the type of the array to Citrus to more closely represent the instances contained within.

3      Event Handling

The C# language has built-in support for event handling.  Event handling is used for notifications such as when a mouse click has happened, or when a menu selection has been made.  Although GUI applications make the most use of event handling, types can use events to allow notification of any kind.

3.1         Handling Events

Handling events is probably the simplest way of working with events.  (There is a fair amount of infrastructure that is not clear until you implement an event, but I will get to that shortly).

using System;

using System.Windows.Forms;

 

class MyForm:Form{

   MyForm(){

      Button button = new Button();

      button.Text = "Button";

      button.Click += new EventHandler(HandleClick);

      Controls.Add(button);

   }

 

   void HandleClick(Object sender, EventArgs e){

      MessageBox.Show("The button's Click event has been raised!");

   }

 

   public static void Main(){

      Application.Run(new MyForm());

   } 

}

Figure 31  EventHand.cs

The code in Figure 31 shows a simple event registration.  In the constructor of MyForm a Button instance is created and added to the form’s controls.  Before this is done, MyForm registers its interest in the button’s Click event.  This is done by adding a new instance of the EventHandler delegate to the Click event as shown in red.  (A delegate is a managed way of encapsulating a callback function.  We will discus delegates more shortly).

The handler function method is called HandleClick() and is shown in red as well.  This method is called each time a user clicks on the button and causes the Click event to be fired.

Here are a couple of points about this code.

·          Notice that the += syntax is used to register your interest in an event.  This is used because it is possible for more than one method to be registered and called when an event is fired.  (To unregister a handler use the -= operator).

·          The parameter list and return type of the HandleClick() method must match the defined parameter list and return type for the EventHandler delegate.  The EventHandler delegate is a type defined by the FCL.

·          The Click event is defined as a member of the Button type in the FCL.  When you define events for your own type’s you may use any delegate you like for the callback method, or you can create your own delegate.  Remember that the delegate defines the signature of the callback method.

3.2         Defining Events

If you are creating a type that needs to allow code to register its interest in an event then you must decide what information you want to pass to the handler methods when the event is raise.  This will affect your choice of delegate type.

The following example defines a delegate and an event.

class EventInt{

   Int32 val;  

 

   public Int32 Value{

      get{return val;}

      set{

         if(Changed != null)

            Changed(value, val);

         val = value;        

      }

   }

 

   public event Callback Changed;

   public delegate void Callback(Int32 newVal, Int32 oldVal);

}

Figure 32  EventInt.cs

The EventInt.cs example is a simple (and most likely useless) type that shows the definition of an event.  The EventInt type implements an Int32 value through a property, and the property fire’s a Changed event if the value is changed.   The firing code is shown in red.

The type defines its own delegate (which is a nested type) named Callback.  The Callback delegate indicates that a handling method must return void and take two Int32 parameters.

The EventInt type also defines an event named Changed which is based on the Callback delegate.  To register your interest in this event you would use the EventInt type in much the same way as the Button type was used in Figure 31.

class App{

   public static void Main(){

      EventInt num = new  EventInt();

 

      num.Changed += new EventInt.Callback(HandleChange);

 

      num.Value = 20;

      num.Value += 10;     

   }

 

   static void HandleChange(Int32 newVal, Int32 oldVal){

      Console.WriteLine("Changed from {0} to {1}", oldVal, newVal); 

   }

}

Figure 33  EventInt consumer

The code in Figure 33 creates an instance of EventInt, and then registers a handler.  Notice that the HandleChange() method matches the definition of the Callback delegate in Figure 32.  In this code example, the HandleChange() method is called twice.

3.3         Callback Methods

Events are very common in managed code, but sometimes you will only need to use a callback.  In this case you can use a delegate, without an event.  Delegates are special types that are defined with special syntax that looks almost like a function definition.

public delegate void Callback(Int32 newVal, Int32 oldVal);

The preceding code defines a type named Callback that can be used to represent a method to be called back. 

The interesting thing about delegates is that you can chain them.  This means that you can register more than one callback and fire it with what looks like a single method call.

using System;

 

delegate void MyDelegate(String message);

 

class App{

   public static void Main(){

      MyDelegate call = new MyDelegate(FirstMethod);

      call += new MyDelegate(SecondMethod);

 

      call("Message A");

      call("Message B");

      call("Message C");

   }

 

   static void FirstMethod(String str){

      Console.WriteLine("1st method: "+str);

   }

 

   static void SecondMethod(String str){

      Console.WriteLine("2nd method: "+str);

   }

}

Figure 34  Delegates.cs

In this example a delegate named MyDelegate is defined, and two instances are created, one-each for the methods FirstMethod and SecondMethod.  Then the delegate chain is called back three times with three different messages.

4      Interfaces

Interfaces are a great feature of object oriented programming.  Some languages such as C++ support a concept called multiple derivation.  The .NET Framework does not allow for multiple inheritance, and as a result neither does C#.  Sometimes, it is nice, however, for a type to be able to take on several roles.  This is where Interfaces come in.

A type is what it is and it is also what its base class is.  But in programming sometimes it is nice to have a well defined role that a type can fulfill, without the role being defined as a base class.  Here is an example.

using System;

 

class SomeType{};

 

class SortType:SomeType, IComparable{

   Int32 val;

   public SortType(Int32 val){

      this.val = val;

   }

  

   public Int32 CompareTo(Object obj){

      return this.val - ((SortType)obj).val;

   }

 

   public override string ToString(){

      return val.ToString();

   }

}

 

class App{

   public static void Main(){

      SomeType[] objs = new SomeType[]{

         new SortType(3), new SortType(1), new SortType(2)};

 

      Array.Sort(objs);

      foreach(SomeType o in objs){

         Console.WriteLine(o.ToString());

      }

   }

}

Figure 41  Sortable.cs

In the Figure 41 a type SortType is derived from a type SomeType.  Even though each instance of SortType continues to also be a SomeType, they have the additional functionality of having implemented the IComparable interface’s CompareTo() method.

It’s this interface that the Sort() method of Array looks for when trying to sort the array of objects.  Inside of the array method, each object of the array is being cast to a reference variable of type IComparable, and then the CompareTo() method is being called, like so.

IComparable ref = (IComparable) obj;

Ref.CompareTo(otherObj);

This is how any object becomes an IComparable just long enough to compare itself (although the instance isn’t really changing at all, but the code’s view of the instance has changed).  Interfaces are a way for objects to have flexible roles while maintaining a strict type.

5      Operator Overloading and Type Conversion

Operator overloading and type conversion are features of C# that allow you to create types that behave like primitive types.  What this means is that you can use the operators such as +, =, and * on your type and the system will call your special methods defined on your type.

5.1         Operator Overloading

Operator overloading in C# is very flexible.  Normally when you create a new type you cannot use operators like + and on instances of the type, because the compiler does not know what to do with the instance in that context.  But, if you define a special static method on the type to indicate what the compiler should do, then the compiler will automatically call your method when it sees the type being used with the matching operator.

using System;

 

class MyType{  

   public static MyType operator +(MyType c1, MyType c2){

      return new MyType();

   }

 

   public static Int32 operator |(MyType c1, MyType c2){

      return 12;

   }

}

 

class App{

   public static void Main(){

      MyType m1 = new MyType();

      MyType m2 = new MyType();

 

      MyType m3 = m1+m2;

      Int32 i = m3|m2;

   }

}

Figure 51  Overloading.cs

In the preceding figure the MyType type implements two static methods to overload the + and | operators for use with instances of MyType.  You can do whatever you like in an operator overload method, just like any other method.

When the compiler sees the code that attempts to add to instances of MyType it calls the method for the + overload.  Likewise it calls the operator for the | overload when it sees the | used with instance of MyType.

Notice that the return value of an operator overload is flexible.  In fact operator overload methods can be defined with any combination of parameters and return values so long as at least one of the parameters is the same type as the containing type.

5.2         Casting Operators (type converters)

Type converters are similar to operator overload methods.  Type converts allow you to assign an instance of one type to a variable of another type using an assignment operator such as =, or you can define a converter that allows you to cast to another type using the cast syntax (type).

using System;

 

class MyType{  

   Int32 val;

 

   public static implicit operator MyType(Int32 i) {

      MyType m = new MyType();

      m.val = i;

      return m;

   }

 

   public static explicit operator Int32(MyType m) {

      return m.val;

   }

}

 

class App{

   public static void Main(){

      MyType m = 10;     

 

      Int32 i = 20 + (Int32)m;

 

      Console.WriteLine(i);     

   }

}

Figure 52  Type Converters

In this example an instance of MyType is created using assignment syntax and the integer value 10.  This is possible, because an implicit casting operator is defined for the MyType.  The casting method is called at the point of assignment, and the method does what it needs to do to return an instance of MyType.

Similarly, an instance of MyType is explicitly cast to an Int32.  The compiler would normally produce an error, except that an explicit cast operator overload is defined for MyType which casts an instance of MyType to an Int32.

You can create as many explicit and implicit cast operator overloads for your type as you like.  The only rule is that either the source or resulting type must be the same type as your type.  The explicit overload will require casting syntax (type), while the implicit overload will make the conversion as needed without the cast syntax.

6      OOP in Practice

C# is a great language for using object oriented programming techniques in your day-to-day projects.  C# has the functionality to allow you to create and derive objects in a way that will make your software more flexible.

The .NET Framework allows you to compose your applications of objects that were built before your application was compiled.  This is similar to Java, but a major departure from C++.  You will see that more and more applications are largely made up of pre-built components (often created by third parties).  Similarly, you will find that in your own code you will write more reusable objects, because the platform is so conducive to this kind of programming.

Object oriented programming takes more than great languages and tools, however.  If you are new to OOP then I suggest you seek as much exposure to the topic as possible, there is no substitute for experience. 

Review the samples from this tutorial, and the other tutorials in the series.  Almost any C# application will exhibit object oriented programming concepts.  Enjoy C# and enjoy object oriented programming.