Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

CSharp Bible (2002) [eng]

.pdf
Скачиваний:
46
Добавлен:
16.08.2013
Размер:
4.29 Mб
Скачать

Distance = MyPoint; System.Console.WriteLine(Distance);

}

}

Note The System.Math.Sqrt() method is defined by the .NET Framework and calculates the square root of the supplied parameter. The method is static, so you can call it without having an object of the System.Math type to call it on.

The Main() method declares an object of type Point and sets its coordinates to (100, 200). It then assigns the object to a variable of type double, which is legal because the Point class defines a conversion operator that converts a Point object to a double. Because the conversion operator is defined as an implicit conversion, no casting is required. The Main() method then prints the value of the converted double to the console.

Figure 10-8 shows the output of Listing 10-8.

Figure 10-8: Defining an implicit conversion

Operators That Cannot Be Overloaded

C# does not enable you to redefine the behavior of the operators in the following list. This is mainly for simplicity's sake. The designers of the C# language wanted these operators kept simple, and to always perform their intended function; therefore, no overloading is allowed.

Assignment

Conditional AND

Conditional OR

Conditional

The new, typeof, sizeof, and is keywords

Summary

C# enables you to tailor the behavior of several of the built-in operators to your own needs. Classes and structures can include methods called operator overload methods that define the behavior of an operator when it appears in an expression with your class or structure.

To overload the unary plus, unary minus, negation, or bitwise complement operators in your class or structure, you define a method with a return type of your choice, the operator being

overloaded, and a single parameter of the type of class or structure containing the overloaded operator method.

To overload the prefix increment or prefix decrement operators in your class or structure, you define a method with a return type specifying the type of class or structure containing the overloaded operator method. You also need to define the operator being overloaded and a single parameter of the type of class or structure containing the overloaded operator method.

To overload the true or false operators in your class or structure, you define a method with a Boolean return type, the operator being overloaded, and a single parameter of the type of class or structure containing the overloaded operator method.

To overload any of the binary operators in your class or structure, you define a method with a return type, the operator being overloaded, and two parameters. At least one of the two parameters must be of the type of class or structure containing the overloaded operator method.

You can also define new conversions for your classes or structures. You specify whether the conversion is to be treated as an implicit operator or an explicit operator. The conversion operator method specifies both the type of the variable being converted as well as the type to which the variable should be converted.

Chapter 11: Class Inheritance

In This Chapter

Simple C# projects may use one or two classes. However, you will most likely write many classes in your larger C# projects. Many of these classes may have similar fields or methods, and it may make sense to share common code among a set of classes.

C# embraces the object-oriented concept of inheritance, which allows one class to inherit code from another class. C# classes can inherit code from parent classes, and the inherited constructs can be used in your own classes.

Inheritance is used in object-oriented software development to re-use common code. Take, for example, the single-selection and multiple-selection list boxes found in Windows. The two list boxes have different functionality - one allows multiple items to be selected and the other doesn't - but they also have many similarities. They both look the same, they both behave the same when the user scrolls through the list, and the color used for a selected item is the same. If you were to write these two list boxes as C# classes, you could write them separately, with each one having no knowledge of the other. However, that would be a waste. Much of the code would be identical. It would make more sense to write a class to contain the common code and have classes that derive from the common code class that implement the different functionality. You can write a C# class called ListBox to hold the common code, for example, and you can then write a C# class called SingleSelectionListBox that inherits from ListBox and supplies the code unique to the single-selection list box. You may also write a C# class called MultipleSelectionListBox that also inherits from ListBox but supplies the code unique to the multiple-selection list box.

Another advantage in this scenario relates to maintaining your code. If you find a bug in your list box, you can trace it back to a bug in the common code. If you can fix the bug in the common code, recompiling your project will fix the bug in both the single-selection and multiple-selection list box classes. One bug fix fixes the problem in two classes.

In object-oriented terminology, inheritance is discussed in terms of a base class and a derived class. The class being inherited from is called the base class, and the class inheriting from the base class is called the derived class. In the list box scenario, the ListBox class is the base class and the SingleSelectionListBox and the MultipleSelectionListBox classes are the derived classes.

Compiling with Multiple Classes

Working with inheritance in C# means that you'll be working with more than one C# class. C# is not picky about how those classes are arranged relative to your source files. You can put all of your classes in a single source file, or you can put each class in a separate source file.

Obviously, in all but the smallest of projects, implementing all the classes in a single file is a poor way of organizing your code. One reason this is a poor idea is that all classes are recompiled every time you make a change anywhere in the program. To compile a project using separate source files from the command line, you simply list each file after the compiler name as follows:

csc file1.cs file2.cs file3.cs

The C# compiler names the output executable after the first source filename by default. The previous compiler command line produces an executable called file1.exe. If you don't like this default, you can use the /out argument to change the output file's name:

csc /out:myapp.exe file1.cs file2.cs file3.cs

This compiler command line produces an executable called myapp.exe.

Note Remember that one, and only one, of your classes must specify a static Main() method.

Specifying a Base Class in C#

Let's return to our Point class example for a look at how inheritance works in C#. Suppose you've designed a class called Point2D, which describes a point in 2D space with X and Y coordinates:

class Point2D

{

public int X; public int Y; // more code

}

Now suppose that you'd like to add support for points in 3D space while still keeping the Point2D class. Inheritance enables you to design a new class that keeps all of the code written for the Point2D class and adds a Z coordinate.

Naming the base class in C# is done by following your derived class name with a colon and the name of the base class. Deriving the Point3D class from the Point2D class looks like the following:

class Point3D : Point2D

{

public int Z;

// code for Point3D class

}

Depending on the base class's scoping rules, which are covered in the "Scope" section of this chapter, all the fields and properties in the base class (Point2D) are available for use in the derived class (Point3D). For example, when a class is derived from a base class, the code in the derived class has access to the fields and properties in the base class, depending on the scope.

You can list only a single base class when inheriting one class from another. Some objectoriented languages, such as C++, allow you to specify more than one base class for a derived class. This concept is called multiple inheritance. C# supports single inheritance but not multiple inheritance. In the section discussing containment. you see a technique to simulate multiple inheritance in C#.

Listing 11-1 shows how the Point3D class and the Point2D class can be used together.

Listing 11-1: Deriving Point3D from Point2D

class Point2D

{

public int X; public int Y;

}

class Point3D : Point2D

{

public int Z;

}

class MyMainClass

{

public static void Main()

{

Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D();

My2DPoint.X = 100;

My2DPoint.Y = 200;

My3DPoint.X = 150;

My3DPoint.Y = 250;

My3DPoint.Z = 350;

}

}

Note that the Main() method creates a Point2D object and a Point3D object. The Point3D object has fields for X, Y, and Z coordinates, although the declaration of Point3D only declares a field called Z. The X and Y fields are inherited from the Point2D base class and can be used just as if they were declared directly in the Point3D class.

Scope

When you design your class inheritance architecture, you may decide that members in your base class should not be visible to derived classes or to the outside world. For example, you may write a method in a base class that helps calculate a value. If that calculation is not useful in a derived class, you may want to prevent the derived class from even calling the method.

In programming terminology, the visibility of a variable or method is referred to as its scope. Some variables or methods may be publicly scoped, others may be privately scoped, and still others may be somewhere in between.

C# defines five keywords that enable you to define the scope of any member (either variable or method) in a class. A member's scope affects its visibility to derived classes and code that creates instances of the class. These keywords, outlined in the following list, are placed before any other keywords in a member declaration.

Members marked public are visible to derived a class and to code that creates objects of the class. We've been using public up to this point.

Members marked private are visible only to the class in which they are defined. Private members are not accessible from derived classes, nor are they accessible from code that creates objects of the class.

Members marked protected are visible only to the class in which they are defined or from classes derived from the class. Protected members are not accessible from code that creates objects of the class.

Members marked internal are visible to any code in the same binary file, but are not visible to any code in other binary files. Remember that the .NET Framework embraces the concept of assemblies, which are libraries of precompiled code that can be used by external applications. If you write a class in C# and compile the class into an assembly, internal class members can be accessed by any piece of code in the assembly. However, if another piece of code uses your assembly, it has no access to the member, even if it derives a class from your assembly class.

Members marked protected internal are visible to any code in the same binary file and to external classes that derive from the class. If you write a class in C# and compile the class into an assembly, internal class members can be accessed by any piece of code in the assembly. If another piece of external code uses your assembly, and derives a class from the class in the assembly, the protected internal member is accessible to the derived class. The member is not, however, accessible to code that works with objects of the base class.

C# enables you to specify a class member without specifying any scope keywords. If you declare a class member without specifying any scope keywords, the member is given private accessibility by default. Members declared without any scope keywords can be used in other

parts of the class, but cannot be used by derived classes or by code that uses objects of the class.

Re-using Member Identifiers in Derived Classes

C# enables you to re-use base class identifiers in derived classes, but the C# compiler issues a warning when this is detected. Consider the code shown in Listing 11-2.

Listing 11-2: Re-using Base Class Identifiers

class Point2D

{

public int X; public int Y;

}

class Point3D : Point2D

{

public int X; public int Y; public int Z;

}

class MyMainClass

{

public static void Main()

{

Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D();

My2DPoint.X = 100;

My2DPoint.Y = 200;

My3DPoint.X = 150;

My3DPoint.Y = 250;

My3DPoint.Z = 350;

}

}

The derived Point3D class defines X and Y fields that clash with the identifiers used in the Point2D base class. The C# compiler issues warnings when this code is compiled:

warning CS0108: The keyword new is required on 'Point3D.X' because it hides inherited member 'Point2D.X'

warning CS0108: The keyword new is required on 'Point3D.Y' because it hides inherited member 'Point2D.Y'

The C# compiler issues the warnings because the identifiers in the derived class hide the definitions using the same identifier in the base class. If you want to re-use the names and want to instruct the C# compiler not to issue the warnings, use the new operator when reusing the identifiers in the derived class. The code shown in Listing 11-3 compiles with no warnings.

Listing 11-3: Using new to Re-use Base Class Identifiers

class Point2D

{

public int X; public int Y;

}

class Point3D : Point2D

{

new public int X; new public int Y; public int Z;

}

class MyMainClass

{

public static void Main()

{

Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D();

My2DPoint.X = 100;

My2DPoint.Y = 200;

My3DPoint.X = 150;

My3DPoint.Y = 250;

My3DPoint.Z = 350;

}

}

Working with Inherited Methods

C# enables methods in base and derived classes to interact in a variety of ways. The language allows for the following method constructs:

Virtual and override methods

Abstract methods

Virtual and override methods

You may want a derived class to change the implementation of a method in a base class while keeping the method name the same. Suppose, for example, that our Point2D class implements a method called PrintToConsole(), which prints the point's X and Y coordinates out to the console. You may also want the derived Point3D class to provide its own implementation of PrintToConsole(). It cannot use the PrintToConsole() method provided in the Point2D class, however, because that implementation only works with X and Y coordinates, and the Point3D class has a Z coordinate as well. The Point3D class must provide its own implementation of the same PrintToConsole() method.

Method names can be re-used in derived classes if the base class method allows the method to be re-implemented. Re-implementing a base class method in a derived class is called overriding the base class method. You need to be aware of two requirements when overriding a base class method in C#:

The base class method must be declared with the keyword virtual.

The derived class method must be declared with the keyword override.

Base class methods using the virtual keyword are called virtual methods, and derived class methods using the override keyword are called override methods.

Listing 11-4 shows how the PrintToConsole() method can be implemented for both the Point2D and the Point3D classes.

Listing 11-4: Overriding Virtual Methods

class Point2D

{

public int X; public int Y;

public virtual void PrintToConsole()

{

System.Console.WriteLine("({0}, {1})", X, Y);

}

}

class Point3D : Point2D

{

public int Z;

public override void PrintToConsole()

{

System.Console.WriteLine("({0}, {1}, {2})", X, Y, Z);

}

}

class MyMainClass

{

public static void Main()

{

Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D();

My2DPoint.X = 100;

My2DPoint.Y = 200;

My3DPoint.X = 150;

My3DPoint.Y = 250;

My3DPoint.Z = 350;

My2DPoint.PrintToConsole();

My3DPoint.PrintToConsole();

}

}

Note The syntax of the WriteLine() calls made in Listing 11-4 is different than the syntax used previously in this book. The numbers in curly brackets in the string are placeholders. The values of the other parameters are written to the console instead of the placeholder. The {0} placeholder is replaced with the value of the first parameter after the string parameter, the {1} placeholder is replaced with the value of the second

parameter after the string parameter, and so on.

Listing 11-4 prints the following to the console:

(100, 200) (150, 250, 350)

You cannot override a base class method unless the base class method uses the virtual keyword. If you try to do this, the C# compiler issues an error:

error CS0506: 'Point3D.PrintToConsole()' : cannot override inherited member 'Point2D.PrintToConsole()' because it is not marked virtual, abstract, or override

You can, however, override an override method. If, for some odd reason, you decide to implement a Point4D class and derive it from Point3D, you can override the Point3D's PrintToConsole() method.

Polymorphism

The concept of overriding methods leads to the concept of polymorphism. When you override a method, you want the correct method called from any methods that call this overridden method.

Listing 11-5 shows this concept in action. You have added a UsePrintToConsole() method to Point2D that calls the virtual method PrintToConsole(). Point3D inherits this method from Point2D. When PrintToConsole() is called in this function, you want to call the version that belongs to the appropriate class. In other words, in the UsePrintToConsole() method that belongs to the Point2D class, you want to call the PrintToConsole() method that belongs to the Point2D class. In the UsePrintToConsole() method that belongs to the Point3D class, you want to call the overridden PrintToConsole() method that belongs to the Point3D class. Because the PrintToConsole() method was declared as a virtual method, this detecting of which version to run happens automatically.

This is exactly what happens, as Listing 11-5 prints the following to the console:

(100, 200) (150, 250, 350)

Listing 11-5: Polymorphism

class Point2D

{

public int X; public int Y;

public virtual void PrintToConsole()

{

System.Console.WriteLine("({0}, {1})", X, Y);

}

public void UsePrintToConsole()

{

PrintToConsole();

}

}

class Point3D : Point2D

{

public int Z;

public override void PrintToConsole()

{

System.Console.WriteLine("({0}, {1}, {2})", X, Y, Z);

}

}

class MyMainClass

{

public static void Main()

{

Point2D My2DPoint = new Point2D(); Point3D My3DPoint = new Point3D();

My2DPoint.X = 100;

My2DPoint.Y = 200;

My3DPoint.X = 150;

My3DPoint.Y = 250;

My3DPoint.Z = 350;

My2DPoint.UsePrintToConsole();

My3DPoint.UsePrintToConsole();

}

}

Abstract methods

Some base classes may not be able to provide an implementation of a method but you may still want to require that derived classes provide an implementation. Suppose, for example, that you're writing a geometry application in C# and write classes called Square and Circle.

You decide upon some common functionality that every shape will use in your application, so you implement a base class called Shape and derive the Square and Circle classes from Shape:

Class Shape

{

}

class Circle : Shape

{

}

class Square : Shape

{

}

Now suppose that you decide that all shapes should be able to calculate their area, so you write a method called GetArea(). The problem with writing that code in the Shape base class

Соседние файлы в предмете Программирование на C++