Defining Classes

>> Aug 19, 2009

In Chapter 8 , you looked at the features of object - oriented programming (OOP). In this chapter, you put theory into practice and define classes in C#. You won ’ t go so far as to define class members in this chapter but will concentrate on the class definitions themselves. That may sound a little limiting, but don ’ t worry — there ’ s plenty here to get your teeth into!
To start, you explore the basic class definition syntax, the keywords you can use to determine class accessibility and more, and the way in which you can specify inheritance. You also look at interface definitions, because they are similar to class definitions in many ways.
The rest of the chapter covers various related topics that apply when defining classes in C#, including the following:
. The System.Objectclass.
. Helpful tools provided by VS and VCE .
. Class libraries .
. A comparison between interfaces and abstract classes .
. Struct types .
. Copying objects .

Class Definitions in C#
C# uses the class keyword to define classes:
class MyClass
{
// Class members.
}


This code defines a class called MyClass. Once you have defined a class, you are free to instantiate it anywhere else in your project that has access to the definition. By default, classes are declared as internal, meaning that only code in the current project will have access to them. You can specify this explicitly using the internal access modifier keyword as follows (although you don ’ t have to):
internal class MyClass
{
// Class members.
}


Alternatively, you can specify that the class is public and should also be accessible to code in other projects. To do so, you use the publickeyword:
public class MyClass
{
// Class members.
}


Classes declared in their own right like this cannot be private or protected, but you can use these modifiers to declare classes as class members, as shown in the next chapter.
In addition to these two access modifier keywords, you can also specify that the class is either abstract (cannot be instantiated, only inherited, and can have abstract members) or sealed (cannot be inherited). To do this, you use one of the two mutually exclusive keywords, abstractor sealed. An abstract class is declared as follows:
public abstract class MyClass
{
// Class members, may be abstract.
}


Here, MyClass is a public abstract class, while internal abstract classes are also possible.
Sealed classes are declared as follows:
public sealed class MyClass
{
// Class members.
}


As with abstract classes, sealed classes may be public or internal.
Inheritance can also be specified in the class definition. You simply put a colon after the class name, followed by the base class name:
public class MyClass : MyBase
{
// Class members.
}


Only one base class is permitted in C# class definitions; and if you inherit from an abstract class, you must implement all the abstract members inherited (unless the derived class is also abstract).
The compiler does not allow a derived class to be more accessible than its base class. This means that an internal class can inherit from a public base, but a public class can ’ t inherit from an internal base. This code is legal:
public class MyBase
{
// Class members.
}
internal class MyClass : MyBase
{
// Class members.
}


The following code won ’ t compile:
internal class MyBase
{
// Class members.
}
public class MyClass : MyBase
{
// Class members.
}


If no base class is used, the class inherits only from the base class System.Object(which has the alias object in C#). Ultimately, all classes have System.Object at the root of their inheritance hierarchy. You take a closer look at this fundamental class a little later.
In addition to specifying base classes in this way, you can also specify interfaces supported after the colon character. If a base class is specified, it must be the first thing after the colon, with interfaces specified afterward. If no base class is specified, you specify the interfaces immediately after the colon. Commas must be used to separate the base class name (if there is one) and the interface names from one another.
For example, you could add an interface to MyClassas follows:
public class MyClass : IMyInterface
{
// Class members.
}


All interface members must be implemented in any class that supports the interface, although you can provide an “ empty ” implementation (with no functional code) if you don ’ t want to do anything with a given interface member, and you can implement interface members as abstract in abstract classes.
The following declaration is invalid, because the base class MyBase isn ’ t the first entry in the inheritance list:
public class MyClass : IMyInterface, MyBase
{
// Class members.
}


The correct way to specify a base class and an interface is as follows:
public class MyClass : MyBase, IMyInterface
{
// Class members.
}


Remember that multiple interfaces are possible, so the following is also valid:
public class MyClass : MyBase, IMyInterface, IMySecondInterface
{
// Class members.
}



The following table shows the allowed access modifier combinations for class definitions:
Interface Definitions
Interfaces are declared in a similar way to classes, but using the interface keyword, rather than class:
interface IMyInterface
{
// Interface members.
}


The access modifier keywords publicand internal are used in the same way; and as with classes, interfaces are defined as internal by default. To make an interface publicly accessible you must use the publickeyword:
public interface IMyInterface
{
// Interface members.
}


The keywords abstractand sealed are not allowed because neither modifier makes sense in the context of interfaces (they contain no implementation, so they can ’ t be instantiated directly, and they must be inheritable to be useful).
Interface inheritance is also specified in a similar way to class inheritance. The main difference here is that multiple base interfaces can be used, as shown here:
public interface IMyInterface : IMyBaseInterface, IMyBaseInterface2
{
// Interface members.
}


Interfaces are not classes, and thus do not inherit from System.Object. However, the members of System.Object are available via an interface type variable, purely for convenience. In addition, as already discussed, it is impossible to instantiate an interface in the same way as a class. The following Try It Out provides an example of some class definitions, along with some code that uses them.

Try It Out : Defining Classes
1. Create a new console application called Ch09Ex01 and save it in the directory C:\BegVCSharp\Chapter09.
2. Modify the code in Program.csas follows:
namespace Ch09Ex01
{
public abstract class MyBase
{
}
internal class MyClass : MyBase
{
}
public interface IMyBaseInterface
{
}
internal interface IMyBaseInterface2
{
}
internal interface IMyInterface : IMyBaseInterface, IMyBaseInterface2
{
}
internal sealed class MyComplexClass : MyClass, IMyInterface
{
}
class Program
{
static void Main(string[] args)
{
MyComplexClass myObj = new MyComplexClass();
Console.WriteLine(myObj.ToString());
Console.ReadKey();
}
}
}


3. Execute the project. The output is shown in Figure 9 - 1. Figure 9-1

How It Works
This project defines classes and interfaces in the inheritance hierarchy shown in Figure 9 - 2. Figure 9-2

Here, Programis included because it is a class defined in the same way as the other classes, even though it isn ’ t part of the main class hierarchy. The Main()method possessed by this class is the entry point for your application.
MyBaseand IMyBaseInterface are public definitions, so they are available from other projects. The other classes and interfaces are internal, and only available in this project.
The code in Main()calls the ToString()method of myObj, an instance of MyComplexClass:
MyComplexClass myObj = new MyComplexClass();
Console.WriteLine(myObj.ToString());


This is one of the methods inherited from System.Object (not shown in the diagram because I ’ ve omitted the members of this class for clarity) and simply returns the class name of the object as a string, qualified by any relevant namespaces.
This example may not actually do a lot, but you will return to it later in this chapter, where it is used to demonstrate several key concepts and techniques.

System.Object
Because all classes inherit from System.Object, all classes have access to the protected and public members of this class. Therefore, it is worthwhile to take a look at what is available there. System.Objectcontains the methods described in the following table:
These are the basic methods that must be supported by object types in the .NET Framework, although you might never use some of them (or use them only in special circumstances, such as GetHashCode).
GetType is helpful when you are using polymorphism because it enables you to perform different operations with objects depending on their type, rather than the same operation for all objects, as is often the case. For example, if you have a function that accepts an objecttype parameter (meaning you can pass it just about anything), you might perform additional tasks if certain objects are encountered. Using a combination of GetTypeand typeof(a C# operator that converts a class name into a System.Type object), you can perform comparisons such as the following:
if (myObj.GetType() == typeof(MyComplexClass))
{
// myObj is an instance of the class MyComplexClass.
}


The System.Type object returned is capable of a lot more than that, but only this is covered here. It can also be very useful to override the ToString method, particularly in situations where the contents of an object can be easily represented with a single human - readable string. You see these System.Object methods repeatedly in subsequent chapters, so you ’ ll learn more details as necessary.

Constructors and Destructors
When you define a class in C#, it ’ s often unnecessary to define associated constructors and destructors, because the compiler adds them for you when you build your code if you don ’ t supply them. However, you can provide your own, if required, which enables you to initialize and clean up after your objects, respectively.
A simple constructor can be added to a class using the following syntax:
class MyClass
{
public MyClass()
{
// Constructor code.
}
}


This constructor has the same name as the class that contains it, has no parameters (making it the default constructor for the class), and is public so that objects of the class may be instantiated using this constructor (refer to Chapter 8 for more information about this).
You can also use a private default constructor, meaning that object instances of this class cannot be created using this constructor (it is noncreatable — again, see the discussion in Chapter 8 ):
class MyClass
{
private MyClass()
{
// Constructor code.
}
}


Finally, you can add nondefault constructors to your class in a similar way, simply by providing parameters:
class MyClass
{
public MyClass()
{
// Default constructor code.
}
public MyClass(int myint)
{
// Nondefault constructor code (uses myint).
}
}


You can supply an unlimited number of constructors (apart from running out of memory or distinct sets f parameters, so maybe “ almost no limit ” is more appropriate).
Destructors are declared using a slightly different syntax. The destructor used in .NET (and supplied by the System.Objectclass) is called Finalize, but this isn ’ t the name you use to declare a destructor. Instead of overriding Finalize, you use the following:
class MyClass
{
~MyClass()
{
// Destructor body.
}
}


Thus, the destructor of a class is declared by the class name (like the constructor is), with the tilde ( ~) prefix. The code in the destructor is executed when garbage collection occurs, enabling you to free resources. After the destructor is called, implicit calls to the destructors of base classes also occur, including a call to Finalizein the System.Object root class. This technique enables the .NET Framework to ensure that this occurs because overriding Finalizewould mean that base class calls would need to be explicitly performed, which is potentially dangerous (you learn how to call base class methods in the next chapter).

Constructor Execution Sequence
If you perform multiple tasks in the constructors of a class, it can be handy to have this code in one place, which has the same benefits as splitting code into functions, as shown in Chapter 6 . You could do this using a method (see Chapter 10 ), but C# provides a nice alternative. You can configure any constructor to call any other constructor before it executes its own code.
First, though, you need to take a closer look at what happens by default when you instantiate a class instance. Apart from facilitating the centralization of initialization code as noted previously, this is worth knowing about in its own right. During development, objects often don ’ t behave quite as you expect them to due to errors during constructor calling — usually due to a base class somewhere in the inheritance hierarchy of your class that you are not instantiating correctly, or because information is not being properly supplied to base class constructors. Understanding what happens during this phase of an object ’ s life cycle can make it much easier to solve this sort of problem.
For a derived class to be instantiated, its base class must be instantiated. For this base class to be instantiated, its own base class must be instantiated, and so on all the way back to System.Object(the root of all classes). As a result, whatever constructor you use to instantiate a class, System.Object . Object is always called first.
Regardless of which constructor you use in a derived class (the default constructor or a nondefault constructor), unless you specify otherwise, the default constructor for the base class is used. (You ’ ll see how to change this behavior shortly.) Here ’ s a short example illustrating the sequence of execution.
Consider the following object hierarchy:
public class MyBaseClass
{
public MyBaseClass()
{
}
public MyBaseClass(int i)
{
}
}
public class MyDerivedClass : MyBaseClass
{
public MyDerivedClass()
{
}
public MyDerivedClass(int i)
{
}
public MyDerivedClass(int i, int j)
{
}
}


You could instantiate MyDerivedClassas follows:
MyDerivedClass myObj = new MyDerivedClass();

In this case, the following sequence of events will occur:
. The System.Object.Object constructor will execute.
. The MyBaseClass.MyBaseClass constructor will execute.
. The MyDerivedClass.MyDerivedClass constructor will execute.

Alternatively, you could use the following:
MyDerivedClass myObj = new MyDerivedClass(4);

Here, the sequence is as follows:
. The System.Object.Object constructor will execute.
. The MyBaseClass.MyBaseClass constructor will execute.
. The MyDerivedClass.MyDerivedClass(int i)constructor will execute.

Finally, you could use this:
MyDerivedClass myObj = new MyDerivedClass(4, 8);

This results in the following sequence:
. The System.Object.Object constructor will execute.
. The MyBaseClass.MyBaseClass constructor will execute.
. The MyDerivedClass.MyDerivedClass(int i, int j)constructor will execute.

This system works fine most of the time, but sometimes you will want a little more control over the events that occur. For example, in the last instantiation example, you might want to have the following sequence:
. The System.Object.Object constructor will execute.
. The MyBaseClass.MyBaseClass(int i)constructor will execute.
. The MyDerivedClass.MyDerivedClass(int i, int j) constructor will execute.

Using this you could place the code that uses the int iparameter in MyBaseClass(int i) , meaning that the MyDerivedClass(int i, int j) constructor would have less work to do — it would only need to process the int j parameter. (This assumes that the int iparameter has an identical meaning in both scenarios, which might not always be the case; but in practice, with this kind of arrangement, it usually is.) C# allows you to specify this kind of behavior if you want.
To do this, you can use a constructor initializer, which consists of code placed after a colon in the method definition. For example, you could specify the base class constructor to use in the definition of the constructor in your derived class as follows:
public class MyDerivedClass : MyBaseClass
{
...
public MyDerivedClass(int i, int j) : base(i)
{
}
}


The base keyword directs the .NET instantiation process to use the base class constructor, which has the specified parameters. Here, you are using a single intparameter (the value of which is the value that is passed to the MyDerivedClass constructor as the parameter i), so MyBaseClass(int i)will be used. Doing this means that MyBaseClasswill not be called, giving you the sequence of events listed prior to this example — exactly what you want here.
You can also use this keyword to specify literal values for base class constructors, perhaps using the default constructor of MyDerivedClass to call a nondefault constructor of MyBaseClass:
public class MyDerivedClass : MyBaseClass
{
public MyDerivedClass() : base(5)
{
}
...
}


This gives you the following sequence:
. The System.Object.Object constructor will execute.
. The MyBaseClass.MyBaseClass(int i)constructor will execute.
. The MyDerivedClass.MyDerivedClass()constructor will execute.

As well as this base keyword, you can use one more keyword as a constructor initializer: this . This keyword instructs the .NET instantiation process to use a nondefault constructor on the current class before the specified constructor is called:
public class MyDerivedClass : MyBaseClass
{
public MyDerivedClass() : this(5, 6)
{
}
...
public MyDerivedClass(int i, int j) : base(i)
{
}
}


Here, you have the following sequence:
. The System.Object.Object constructor will execute.
. The MyBaseClass.MyBaseClass(int i)constructor will execute.
. The MyDerivedClass.MyDerivedClass(int i, int j) constructor will execute.
. The MyDerivedClass.MyDerivedClass constructor will execute.

The only limitation here is that you can only specify a single constructor using a constructor initializer. However, as demonstrated in the last example, this isn ’ t much of a limitation, because you can still
construct fairly sophisticated execution sequences.
If you don ’t specify a constructor initializer for a constructor, the compiler adds one for you: base(). This results in the default behavior described earlier in this section.

OOP Tools in VS and VCE
Because OOP is such a fundamental subject in the .NET Framework, several tools are provided by VS and VCE to aid development of OOP applications. This section describes some of these.

The Class View Window
In Chapter 2 , you saw that the Solution Explorer window shares space with a window called Class View. This window shows you the class hierarchy of your application and enables you to see at a glance the characteristics of the classes you use. Figure 9 - 3 shows the view for the example project in the previous Try It Out. Figure 9-3

The window is divided into two main sections; the bottom section shows members of types. To see this in action with this example project, and to see what else is possible with the Class View window, you need to show some items that are currently hidden. To do this, tick the items in the Class View Grouping drop - down at the top of the Class View window, as shown in Figure 9 - 4. Figure 9-4

Now you can see members and additional information, as shown in Figure 9 - 5. Figure 9-5

Many symbols may be used here, including the following icons:Note that some of these are used for type definitions other than classes, such as enumerations and struct types. Some of the entries may have other symbols placed below them signifying their access level (no symbol appears for public entries). These symbols are shown here:No symbols are used to denote abstract, sealed, or virtual entries.
As well as being able to look at this information here, you can also access the relevant code for many of these items. Double - clicking on an item, or right - clicking and selecting Go To Definition, takes you straight to the code in your project that defines the item, if it is available. If the code isn ’ t available, such as code in an inaccessible base type (e.g., System.Object), you instead have the option to select Browse Definition, which will take you to the Object Browser view (described in the next section).
One other entry that appears in Figure 9 - 5 is Project References. This enables you to see what assemblies are referenced by your projects, which in this case includes the core .NET types in mscorliband system, data access types in system.data, and XML manipulation types in system.xml . The references here can be expanded, showing you the namespaces and types contained within these assemblies.
There is also a function available from the Class View that enables you to find occurrences of types and members in your code. These are available by right - clicking on an item and selecting Find All References. Either option provides a list of search results in the Find Symbol Results window, which appears at the bottom of the screen as a tabbed window in the Error List display area. You can also rename items using the Class View. If you do this, you ’ re given the option to rename references to the item wherever it occurs in your code. This means you have no excuse for spelling mistakes in class names because you can change them as often as you like!

The Object Browser
The Object Browser is an expanded version of the Class View window, enabling you to view other classes available to your project, and even completely external classes. It is entered either automatically (e.g., in the situation noted in the last section) or manually via View > Other Windows > Object Browser.
The view appears in the main window, and you can browse it in the same way as the Class View window.
This window provides the same information as Class View but also shows you more of the .NET types. When an item is selected, you also get information about it in a third window, as shown in Figure 9 - 6. Figure 9-6

Here, the ReadKeymethod of the Consoleclass has been selected. (Consoleis found in the System namespace in the mscorlib assembly.) The information window in the bottom, right corner shows you the method signature, the class to which the method belongs, and a summary of the method function. This information can be useful when you are exploring the .NET types, or if you are just refreshing your memory about what a particular class can do.
In addition, you can make use of this information window in types that you create. Make the following change to the code in Ch09Ex01:
/// < summary >
/// This class contains my program!
/// < /summary >
class Program
{
static void Main(string[] args)
{
MyComplexClass myObj = new MyComplexClass();
Console.WriteLine(myObj.ToString());
Console.ReadKey();
}
}


Return to the Object Browser. The change is reflected in the information window. This is an example of XML documentation, the subject of Chapter 31 .
If you made this code change manually, then you noticed that simply typing the three slashes /// causes the IDE to add most of the rest of the code for you. It automatically analyzes the code to which you are applying XML documentation and builds the basic XML documentation — more evidence, should you need any, that VS and VCE are great tools to work with!

Adding Classes
VS and VCE contain tools that can speed up some common tasks, and some of these are applicable to OOP. One of these tools, the Add New Item Wizard, enables you to add new classes to your project with a minimum amount of typing.
This tool is accessible through the Project > Add New Item menu item or by right - clicking on your project in the Solution Explorer window and selecting the appropriate item. Either way, a dialog appears, enabling you to choose the item to add. The default display for this window varies between VS and VCE but the functionality is the same. In VCE the display defaults to a selection of large icons; VCE shows a list with smaller icons. In both IDEs, to add a class, select the Class item in the Templates window, as shown in Figure 9 - 7 , provide a filename for the file that will contain the class, and click Add. The class created is named according to the filename you provided. Figure 9-7

In the Try It Out earlier in this chapter, you added class definitions manually to your Program.csfile. Often, keeping classes in separate files makes it easier to keep track of your classes. Entering the information in the Add New Item dialog when the Ch09Ex01 project is open results in the following code being generated in MyNewClass.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Ch09Ex01
{
class MyNewClass
{
}
}


This class, MyNewClass, is defined in the same namespace as your entry point class, Program, so you can use it from code just as if it were defined in the same file. As shown in the code, the class generated for you contains no constructor. Recall that if a class definition doesn ’ t include a constructor, then the compiler adds a default constructor when you compile your code.

Class Diagrams
One powerful feature of VS that you haven ’ t looked at yet is the capability to generate class diagrams from code and use them to modify projects. The class diagram editor in VS enables you to generate UML - like diagrams of your code with ease. You ’ ll see this in action in the following Try It Out when you generate a class diagram for the Ch09Ex01 project you created earlier.
Unfortunately, class diagrams are a feature that is missing from VCE, so you can only follow this Try It Out if you have VS.

Try It Out : Generating a Class Diagram
1. Open the Ch09Ex01 project created earlier in this chapter.
2. In the Solution Explorer window, select Program.cs and then click the View Class Diagram button in the toolbar, as shown in Figure 9 - 8. Figure 9-8
3. A class diagram appears, called ClassDiagram1.cd.
4. Click on the IMyInterface lollipop and, using the Properties window, change its Position property to Right.
5. Right - click on MyBase and select Show Base Type from the context menu.
6. Move the objects in the drawing around by dragging them to achieve a more pleasing layout. At this point, the diagram should look a little like Figure 9 - 9. Figure 9-9

How It Works
With very little effort, you have created a class diagram not unlike the UML diagram presented in Figure 9 - 2 (which doesn ’ t show the color, of course). The following features are evident:
. Classes are shown as blue boxes, including their name and type.
. Interfaces are shown as green boxes, including their name and type.
. Inheritance is shown with arrows with white heads (and in some cases text inside class boxes).
. Classes implementing interfaces have lollipops.
. Abstract classes are shown with a dotted outline and italicized name.
. Sealed classes are shown with a thick black outline.

Clicking on an object shows you additional information in a Class Details window at the bottom of the screen. Here, you can see (and modify) class members. You can also modify class details in the Properties window.
Chapter 10 takes a detailed look at adding members to classes using the class diagram.
From the Toolbox, you can add new items such as classes, interfaces, and enums to the diagram, and define relationships between objects in the diagram. When you do this, the code for the new items is automatically generated for you.
Using this editor, you can design whole families of types graphically, without ever having to use the code editor. Obviously, when it comes to actually adding the functionality you have to do things by hand, but this is a great way to get started. You ’ ll return to this view in subsequent chapters and learn more about what it can do for you, including using the Object Test Bench to test your classes before using them in your code. For now, though, you can explore things on your own.

Class Library Projects
As well as placing classes in separate files within your project, you can also place them in completely separate projects. A project that contains nothing but classes (along with other relevant type definitions, but no entry point) is called a class library.
Class library projects compile into . dllassemblies, and you can access their contents by adding references to them from other projects (which might be part of the same solution, but doesn ’ t have to be). This extends the encapsulation that objects provide because class libraries may be revised and updated without touching the projects that use them, enabling you to easily upgrade services provided by classes (which might affect multiple consumer applications).
The following Try It Out provides an example of a class library project and a separate project that makes use of the classes that it contains.

Try It Out : Using a Class Library
1. Create a new project of type Class Library called Ch09ClassLib and save it in the directory C:\BegVCSharp\Chapter09, as shown in Figure 9 - 10. Figure 9-10
2. Rename the file Class1.csto MyExternalClass.cs (by right - clicking on the file in the Solution Explorer window and selecting Rename). Click Yes on the dialog that appears.
3. The code in MyExternalClass.cs automatically changes to reflect the class name change:
class MyExternalClass
{
}

4. Add a new class to the project, using the filename MyInternalClass.cs.
5. Modify the code to make the class MyInternalClassexplicitly internal:
internal class MyInternalClass
{
}

6. Compile the project (this project has no entry point, so you can ’ t run it as normal — instead, you can build it by selecting Build > Build Solution).
7. Create a new console application project called Ch09Ex02 and save it in the directory C:\BegVCSharp\Chapter09.
8. Select Project > Add Reference, or select the same option after right - clicking on References in the Solution Explorer window.
9. Click the Browse tab, navigate to C:\BegVCSharp\Chapter09\Chapter09\Ch09ClassLib\bin\Debug\, and double - click on Ch09ClassLib.dll.
10. When the operation completes, confirm that a reference was added in the Solution Explorer window, as shown in Figure 9 - 11. Figure 9-11
11. Open the Object Browser window and examine the new reference to see what objects it contains (see Figure 9 - 12 ). Figure 9-12
12. Modify the code in Program.csas follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ch09ClassLib;
namespace Ch09Ex02
{
class Program
{
static void Main(string[] args)
{
MyExternalClass myObj = new MyExternalClass();
Console.WriteLine(myObj.ToString());
Console.ReadKey();
}
}
}

13. Run the application. The result is shown in Figure 9 - 13. Figure 9-13

How It Works
This example created two projects: a class library project and a console application project. The class library project, Ch09ClassLib, contains two classes: MyExternalClass, which is publicly accessible, and MyInternalClass, which is internally accessible. Note that the class was implicit by default when you created it, as it had no access modifier. It is good practice to be explicit about accessibility, though, because it makes your code more readable, which is why you add the internalkeyword. The console application project, Ch09Ex02, contains simple code that makes use of the class
library project.
When an application uses classes defined in an external library, you can call that application a client application of the library. Code that uses a class that you define is similarly often referred to as client code .
To use the classes in Ch09ClassLib, you added a reference to Ch09ClassLib.dllto the console application. For the purposes of this example, you simply point at the output file for the class library, although it would be just as easy to copy this file to a location local to Ch09Ex02, enabling you to continue development of the class library without affecting the console application. To replace the old assembly version with the new one, simply copy the newly generated DLL file over the old one.
After adding the reference you took a look at the available classes using the object browser. Because the MyInternalClass is internal, you can ’ t see it in this display — it isn ’ t accessible to external projects. However, MyExternalClass is accessible, and it ’ s the one you use in the console application.
You could replace the code in the console application with code attempting to use the internal class as follows:
static void Main(string[] args)
{
MyInternalClass myObj = new MyInternalClass();
Console.WriteLine(myObj.ToString());
Console.ReadKey();
}


If you attempt to compile this code, you receive the following compilation error:
‘Ch09ClassLib.MyInternalClass’ is inaccessible due to its protection level

This technique of making use of classes in external assemblies is key to programming with C# and the .NET Framework. It is, in fact, exactly what you are doing when you use any of the classes in the .NET Framework because they are treated in the same way.

Interfaces Versus Abstract Classes
This chapter has demonstrated how you can create both interfaces and abstract classes (without members for now — you get to them in Chapter 10 ). The two types are similar in a number of ways, so it would be useful to know how to determine when you would want to use one technique or the other.
First the similarities: Both abstract classes and interfaces may contain members that can be inherited by a derived class. Neither interfaces nor abstract classes may be directly instantiated, but you can declare variables of these types. If you do, you can use polymorphism to assign objects that inherit from these types to variables of these types. In both cases, you can then use the members of these types through these variables, although you don ’ t have direct access to the other members of the derived object.
Now the differences: Derived classes may only inherit from a single base class, which means that only a single abstract class can be inherited directly (although it is possible for a chain of inheritance to include multiple abstract classes). Conversely, classes can use as many interfaces as they want, but this doesn ’ t make a massive difference — similar results can be achieved either way. It ’ s just that the interface way of doing things is slightly different.
Abstract classes may possess both abstract members (these have no code body and must be implemented in the derived class unless the derived class is itself abstract) and non - abstract members (these possess a code body, and can be virtual so that they may be overridden in the derived class). Interface members, conversely, must be implemented on the class that uses the interface — they do not possess code bodies. Moreover, interface members are by definition public (because they are intended for external use), but members of abstract classes may also be private (as long as they aren ’ t abstract), protected, internal, or protected internal (where protected internal members are accessible only from code within the application or from a derived class). In addition, interfaces can ’ t contain fields, constructors, destructors, static members, or constants.
This indicates that the two types are intended for different purposes. Abstract classes are intended for use as the base class for families of objects that share certain central characteristics, such as a common purpose and structure. Interfaces are intended for use by classes that might differ on a far more fundamental level, but can still do some of the same things.
For example, consider a family of objects representing trains. The base class, Train, contains the core definition of a train, such as wheel gauge and engine type (which could be steam, diesel, and so on). However, this class is abstract, because there is no such thing as a “ generic ” train. To create an “ actual ” train, you add characteristics specific to that train. For example, you derive classes such as PassengerTrain, FreightTrain, and 424DoubleBogey, as shown in Figure 9 - 14. Figure 9-14

A family of car objects might be defined in the same way, with an abstract base class of Carand derived classes such as Compact, SUV, and PickUp. Carand Train might even derive from a common base class, such as Vehicle. This is shown in Figure 9 - 15. Figure 9-15

Some of the classes lower in the hierarchy may share characteristics because of their purpose, not just because of what they are derived from. For example, PassengerTrain, Compact, SUV, and Pickupare all capable of carrying passengers, so they might possess an IPassengerCarrierinterface. FreightTrainand PickUpcan carry heavy loads, so they might both have an IHeavyLoadCarrier interface as well. This is illustrated in Figure 9 - 16. Figure 9-16

By breaking down an object system in this way before going about assigning specifics, you can clearly see which situations should use abstract classes rather than interfaces, and vice versa. The result of this example couldn ’ t be achieved using only interfaces or only abstract inheritance.

Struct Types
Chapter 8 noted that structs and classes are very similar but that structs are value types and classes are reference types. What does this actually mean to you? Well, the easiest way of looking at this is with an example, such as the following Try It Out.

Try It Out : Classes versus Structs
1. Create a new console application project called Ch09Ex03 and save it in the directory C:\BegVCSharp\Chapter09.
2. Modify the code as follows:
namespace Ch09Ex03
{
class MyClass
{
public int val;
}
struct myStruct
{
public int val;
}
class Program
{
static void Main(string[] args)
{
MyClass objectA = new MyClass();
MyClass objectB = objectA;
objectA.val = 10;
objectB.val = 20;
myStruct structA = new myStruct();
myStruct structB = structA;
structA.val = 30;
structB.val = 40;
Console.WriteLine(“objectA.val = {0}”, objectA.val);
Console.WriteLine(“objectB.val = {0}”, objectB.val);
Console.WriteLine(“structA.val = {0}”, structA.val);
Console.WriteLine(“structB.val = {0}”, structB.val);
Console.ReadKey();
}
}
}


3. Run the application. The output is shown in Figure 9 - 17. Figure 9-17

How It Works
This application contains two type definitions: one for a struct called myStruct, which has a single public intfield called val, and one for a class called MyClassthat contains an identical field (you look at class members such as fields in Chapter 10 ; for now just understand that the syntax is the same here). Next, you perform the same operations on instances of both of these types:
1. Declare a variable of the type.
2. Create a new instance of the type in this variable.
3. Declare a second variable of the type.
4. Assign the first variable to the second variable.
5. Assign a value to the valfield in the instance in the first variable.
6. Assign a value to the valfield in the instance in the second variable.
7. Display the values of the valfields for both variables.

Although you are performing the same operations on variables of both types, the outcome is different. When you display the values of the val field, both object types have the same value, whereas the struct types have different values. What has happened?
Objects are reference types. When you assign an object to a variable you are actually assigning that variable with a pointer to the object it refers to. A pointer, in real code terms, is an address in memory. In this case, the address is the point in memory where the object is found. When you assign the first object reference to the second variable of type MyClass with the following line, you are actually
copying this address:
MyClass objectB = objectA;

This means that both variables contain pointers to the same object.
Structs are value types. Instead of the variable holding a pointer to the struct, the variable contains the struct itself. When you assign the first struct to the second variable of type myStructwith the
following line, you are actually copying all the information from one struct to the other:
myStruct structB = structA;

You saw behavior like this earlier in this book for simple variable types such as int. The upshot is that the two struct type variables contain different structs. The entire technique of using pointers is hidden from you in managed C# code, making your code much simpler. It is possible to access lower- level operations such as pointer manipulation in C# using unsafe code, but that is an advanced topic not covered here.

Shallow Copying versus Deep Copying
Copying objects from one variable to another by value instead of by reference (that is, copying them in the same way as structs) can be quite complex. Because a single object may contain references to many other objects, such as field members and so on, a lot of processing may be involved. Simply copying each member from one object to another may not work because some of these members might be reference types in their own right.
The .NET Framework takes this into account. Simple object copying by members is achievable through the method MemberwiseClone, inherited from System.Object. This is a protected method, but it would be easy to define a public method on an object that called this method. This copying method is known as a shallow copy, in that it doesn ’ t take reference type members into account. This means that reference members in the new object refer to the same objects as equivalent members in the source object, which isn ’ t ideal in many cases. If you want to create new instances of the members in question by copying the values across (rather than the references), you need to perform a deep copy.
There is an interface you can implement that enables you to do this in a standard way: ICloneable . If you use this interface, then you must implement the single method it contains, Clone. This method returns a value of type System.Object. You can use whatever processing you want to obtain this object, by implementing the method body however you choose. That means you can implement a deep copy if you want to (although the exact behavior isn ’ t mandatory, so you could perform a shallow copy if desired). You take a closer look at this in Chapter 11 .

Summary
This chapter showed how you can define classes and interfaces in C#, putting the theory from the last chapter into a more concrete form. You ’ ve learned the C# syntax required for basic declarations as well as the accessibility keywords you can use, the way in which you can inherit from interfaces and other classes, how to define abstract and sealed classes to control this inheritance, and how to define constructors and destructors.
You then looked at System.Object, the root base class of any class that you define. It supplies several methods, some of which are virtual, so you can override their implementation. This class also enables you to treat any object instance as an instance of this type, enabling polymorphism with any object.
You also examined some of the tools supplied by VS and VCE for OOP development, including the Class View window, the Object Browser window, and a quick way to add new classes to a project. As an extension of this multifile concept, you learned how to create assemblies that can ’ t be executed, but that contain class definitions that you can use in other projects.
After that, you took a more detailed look at abstract classes and interfaces, including their similarities and differences, and situations in which you use one or the other.
Finally, you revisited the subject of reference and value types, looking at structs (the value type equivalent of objects) in slightly more detail. This led to a discussion about shallow and deep copying of objects, a subject covered in more detail later in the book.
The next chapter looks at defining class members, such as properties and methods, which will enable you to take OOP in C# to the level required to create real applications.

Exercises
1. What is wrong with the following code?
public sealed class MyClass
{
// Class members.
}
public class myDerivedClass : MyClass
{
// Class members.
}


2. How would you define a noncreatable class?
3. Why are noncreatable classes still useful? How do you make use of their capabilities?
4. Write code in a class library project called Vehicles that implements the Vehiclefamily of objects discussed earlier in this chapter. There are nine objects and two interfaces that require implementation.
5. Create a console application project, Traffic, that references Vehicles.dll (created in question 4). Include a function called AddPassengerthat accepts any object with the IPassengerCarrier interface. To prove that the code works, call this function using instances of each object that supports this interface, calling the ToString method inherited from System.Object on each one and writing the result to the screen.

Post a Comment

About This Blog

  © Blogger template Simple n' Sweet by Ourblogtemplates.com 2009

Back to TOP