Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
484,19 KB
Nội dung
270 Part II Understanding the C# Language Duplication in code is a warning sign. If possible, you should refactor the code to avoid this duplication and reduce any maintenance costs. One way to achieve this refactoring is to put the common implementation into a new class created specifically for this purpose. In effect, you can insert a new class into the class hierarchy. For example: class GrazingMammal : Mammal, IGrazable { void IGrazable.ChewGrass() { Console.WriteLine("Chewing grass"); // common code for chewing grass } } class Horse : GrazingMammal, ILandBound { } class Sheep : GrazingMammal, ILandBound { } This is a good solution, but there is one thing that is still not quite right: you can actually create instances of the GrazingMammal class (and the Mammal class for that matter). This doesn’t really make sense. The GrazingMammal class exists to provide a common default implementation. Its sole purpose is to be inherited from. The GrazingMammal class is an abstraction of common functionality rather than an entity in its own right. To declare that creating instances of a class is not allowed, you must explicitly declare that the class is abstract, by using the abstract keyword. For example: abstract class GrazingMammal : Mammal, IGrazable { } If you try to instantiate a GrazingMammal object, the code will not compile: GrazingMammal myGrazingMammal = new GrazingMammal( ); // illegal Abstract Methods An abstract class can contain abstract methods. An abstract method is similar in principle to a virtual method (which you met in Chapter 12) except that it does not contain a method body. Chapter 13 Creating Interfaces and Defining Abstract Classes 271 A derived class must override this method. The following example defines the DigestGrass method in the GrazingMammal class as an abstract method; grazing mammals might use the same code for chewing grass, but they must provide their own implementation of the DigestGrass method. An abstract method is useful if it does not make sense to provide a default implementation in the abstract class and you want to ensure that an inheriting class provides its own implementation of that method. abstract class GrazingMammal : Mammal, IGrazable { abstract void DigestGrass(); } Sealed Classes Using inheritance is not always easy and requires forethought. If you create an interface or an abstract class, you are knowingly writing something that will be inherited from in the future. The trouble is that predicting the future is a difficult business. With practice and experience, you can develop the skills to craft a flexible, easy-to-use hierarchy of interfaces, abstract classes, and classes, but it takes effort and you also need a solid understanding of the prob- lem you are modeling. To put it another way, unless you consciously design a class with the intention of using it as a base class, it’s extremely unlikely that it will function very well as a base class. C# allows you to use the sealed keyword to prevent a class from being used as a base class if you decide that it should not be. For example: sealed class Horse : GrazingMammal, ILandBound { } If any class attempts to use Horse as a base class, a compile-time error will be generated. Note that a sealed class cannot declare any virtual methods and that an abstract class cannot be sealed. Note A structure is implicitly sealed. You can never derive from a structure. Sealed Methods You can also use the sealed keyword to declare that an individual method in an unsealed class is sealed. This means that a derived class cannot then override the sealed method. You 272 Part II Understanding the C# Language can seal only an override method, and you declare the method as sealed override, which means that you cannot seal a method that is directly implementing a method in an interface. (You cannot override a method inherited directly from an interface, only from a class.) You can think of the interface, virtual, override, and sealed keywords as follows: n An interface introduces the name of a method. n A virtual method is the first implementation of a method. n An override method is another implementation of a method. n A sealed method is the last implementation of a method. Implementing and Using an Abstract Class The following exercises use an abstract class to rationalize some of the code that you developed in the previous exercise. The Square and Circle classes contain a high propor- tion of duplicate code. It makes sense to factor this code out into an abstract class called DrawingShape because this will ease maintenance of the Square and Circle classes in the future. Create the DrawingShape abstract class 1. Return to the Drawing project in Visual Studio. Note A finished working copy of the previous exercise is available in the Drawing project located in the \Microsoft Press\Visual CSharp Step By Step\Chapter 13\Drawing Using Interfaces - Complete folder in your Documents folder. 2. On the Project menu, click Add Class. The Add New Item – Drawing dialog box appears. 3. In the Name text box, type DrawingShape.cs, and then click Add. Visual Studio creates the file and displays it in the Code and Text Editor window. 4. In the DrawingShape.cs file, add the following using statements to the list at the top: using System.Windows; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Controls; 5. The purpose of this class is to contain the code common to the Circle and Square class- es. A program should not be able to instantiate a DrawingShape object directly. Modify the definition of the DrawingShape class, and declare it as abstract, as shown here in bold: Chapter 13 Creating Interfaces and Defining Abstract Classes 273 abstract class DrawingShape { } 6. Add the private variables shown in bold to the DrawingShape class: abstract class DrawingShape { protected int size; protected int locX = 0, locY = 0; protected Shape shape = null; } The Square and Circle classes both use the locX and locY fields to specify the location of the object on the canvas, so you can move these fields to the abstract class. Similarly, the Square and Circle classes both used a field to indicate the size of the object when it was rendered; although it has a different name in each class (sideLength and radius), se- mantically the field performed the same task in both classes. The name “size” is a good abstraction of the purpose of this field. Internally, the Square class uses a Rectangle object to render itself on the canvas, and the Circle class uses an Ellipse object. Both of these classes are part of a hierarchy based on the abstract Shape class in the .NET Framework. The DrawingShape class uses a Shape field to represent both of these types. 7. Add the following constructor to the DrawingShape class: public DrawingShape(int size) { this.size = size; } This code initializes the size field in the DrawingShape object. 8. Add the SetLocation and SetColor methods to the DrawingShape class, as shown in bold. These methods provide implementations that are inherited by all classes that derive from the DrawingShape class. Notice that they are not marked as virtual, and a derived class is not expected to override them. Also, the DrawingShape class is not declared as implementing the IDraw or IColor interfaces (interface implementation is a feature of the Square and Circle classes and not this abstract class), so these methods are simply declared as public. abstract class DrawingShape { public void SetLocation(int xCoord, int yCoord) { this.locX = xCoord; this.locY = yCoord; } 274 Part II Understanding the C# Language public void SetColor(Color color) { if (shape != null) { SolidColorBrush brush = new SolidColorBrush(color); shape.Fill = brush; } } } 9. Add the Draw method to the DrawingShape class. Unlike the previous methods, this method is declared as virtual, and any derived classes are expected to override it to ex- tend the functionality. The code in this method verifies that the shape field is not null, and then draws it on the canvas. The classes that inherit this method must provide their own code to instantiate the shape object. (Remember that the Square class creates a Rectangle object and the Circle class creates an Ellipse object.) abstract class DrawingShape { public virtual void Draw(Canvas canvas) { if (this.shape == null) { throw new ApplicationException(“Shape is null”); } this.shape.Height = this.size; this.shape.Width = this.size; Canvas.SetTop(this.shape, this.locY); Canvas.SetLeft(this.shape, this.locX); canvas.Children.Add(shape); } } You have now completed the DrawingShape abstract class. The next step is to change the Square and Circle classes so that they inherit from this class, and remove the duplicated code from the Square and Circle classes. Modify the Square and Circle classes to inherit from the DrawingShape class 1. Display the code for the Square class in the Code and Text Editor window. Modify the definition of the Square class so that it inherits from the DrawingShape class as well as implementing the IDraw and IColor interfaces. class Square : DrawingShape, IDraw, IColor { } Chapter 13 Creating Interfaces and Defining Abstract Classes 275 Notice that you must specify the class that the Square class inherits from before any interfaces. 2. In the Square class, remove the definitions of the sideLength, rect, locX, and locY fields. 3. Replace the existing constructor with the following code, which calls the constructor in the base class. Notice that the body of this constructor is empty because the base class constructor performs all the initialization required. class Square : DrawingShape, IDraw, IColor { public Square(int sideLength) : base(sideLength) { } } 4. Remove the SetLocation and SetColor methods from the Square class. The DrawingShape class now provides the implementation of these methods. 5. Modify the definition of the Draw method. Declare it as public override, and remove the reference to the IDraw interface. Again, the DrawingShape class already provides the base functionality for this method, but you will extend it with specific code required by the Square class. public override void Draw(Canvas canvas) { } 6. Replace the body of the Draw method with the code shown in bold. These statements instantiate the shape field inherited from the DrawingShape class as a new instance of the Rectangle class if it has not already been instantiated, and then they call the Draw method in the DrawingShape class. public override void Draw(Canvas canvas) { if (this.shape != null) { canvas.Children.Remove(this.shape); } else { this.shape = new Rectangle(); } base.Draw(canvas); } 7. Repeat steps 2 through 6 for the Circle class, except that the constructor should be called Circle with a parameter called radius, and in the Draw method you should 276 Part II Understanding the C# Language instantiate the shape field as a new Ellipse object. The complete code for the Circle class should look like this: class Circle : DrawingShape, IDraw, IColor { public Circle(int radius) : base(radius) { } public override void Draw(Canvas canvas) { if (this.shape != null) { canvas.Children.Remove(this.shape); } else { this.shape = new Ellipse(); } base.Draw(canvas); } } 8. On the Debug menu, click Start Without Debugging. When the Drawing Pad window appears, verify that Square objects appear when you left-click in the window and Circle objects appear when you right-click in the window. 9. Close the Drawing Pad window, and return to Visual Studio. In this chapter, you have seen how to define and implement interfaces and abstract classes. The following table summarizes the various valid (yes), invalid (no), and mandatory (required) keyword combinations when defining methods for interfaces and classes. Keyword Interface Abstract class Class Sealed class Structure abstract no yes no no no new yes 1 yes yes yes no 2 override no yes yes yes no 3 private no yes yes yes yes protected no yes yes yes no 4 public no yes yes yes yes sealed no yes yes required no virtual no yes yes no no 1 An interface can extend another interface and introduce a new method with the same signature. 2 A structure implicitly derives from System.Object, which contains methods that the structure can hide. 3 A structure implicitly derives from System.Object, which contains no virtual methods. 4 A structure is implicitly sealed and cannot be derived from. Chapter 13 Creating Interfaces and Defining Abstract Classes 277 n If you want to continue to the next chapter Keep Visual Studio 2010 running, and turn to Chapter 14. n If you want to exit Visual Studio 2010 now On the File menu, click Exit. If you see a Save dialog box, click Yes and save the project. Chapter 13 Quick Reference To Do this Declare an interface Use the interface keyword. For example: interface IDemo { string Name(); string Description(); } Implement an interface Declare a class using the same syntax as class inheritance, and then im- plement all the member functions of the interface. For example: class Test : IDemo { public string IDemo.Name() { } public string IDemo.Description() { } } Create an abstract class that can be used only as a base class, containing abstract methods Declare the class using the abstract keyword. For each abstract method, declare the method with the abstract keyword and without a method body. For example: abstract class GrazingMammal { abstract void DigestGrass(); } Create a sealed class that cannot be used as a base class Declare the class using the sealed keyword. For example: sealed class Horse { } 279 Chapter 14 Using Garbage Collection and Resource Management After completing this chapter, you will be able to: n Manage system resources by using garbage collection. n Write code that runs when an object is finalized by using a destructor. n Release a resource at a known point in time in an exception-safe manner by writing a try/finally statement. n Release a resource at a known point in time in an exception-safe manner by writing a using statement. You have seen in earlier chapters how to create variables and objects, and you should understand how memory is allocated when you create variables and objects. (In case you don’t remember, value types are created on the stack, and reference types are allocated memory from the heap.) Computers do not have infinite amounts of memory, so memory must be reclaimed when a variable or an object no longer needs it. Value types are de- stroyed and their memory reclaimed when they go out of scope. That’s the easy bit. How about reference types? You create an object by using the new keyword, but how and when is an object destroyed? That’s what this chapter is all about. The Life and Times of an Object First, let’s recap what happens when you create an object. You create an object by using the new operator. The following example creates a new instance of the Square class that you met in Chapter 13, “Creating Interfaces and Defining Abstract Classes”: Square mySquare = new Square(); // Square is a reference type From your point of view, the new operation is atomic, but underneath, object creation is really a two-phase process: 1. The new operation allocates a chunk of raw memory from the heap. You have no control over this phase of an object’s creation. 2. The new operation converts the chunk of raw memory to an object; it has to initialize the object. You can control this phase by using a constructor. [...]... will be bypassed You will rewrite the code to use a using statement instead, ensuring that the code is exception safe Write a using statement 1 Start Microsoft Visual Studio 2010 if it is not already running 2 Open the UsingStatement project, located in the \Microsoft Press \Visual CSharp Step By Step\ Chapter 14\UsingStatement folder in your Documents folder 290 Part II Understanding the C# Language... Open dialog box, move to the \Microsoft Press \Visual CSharp Step By Step\ Chapter 14\UsingStatement\UsingStatement folder in your Documents folder, and s elect the MainWindow.xaml.cs source file This is the source file for the application itself 6 Click Open The contents of the file are displayed in the form, as shown here: 7 Close the form to return to Visual Studio 2010 8 Open the MainWindow.xaml.cs... it explicitly from the program For example: class TextReader { public virtual void Close() { } } class Example { void Use() { TextReader reader = ; // use reader reader.Close(); } } Microsoft Visual C# 2010 Step by Step Part III Creating Components In this part: Implementing Properties to Access Fields Using Indexers ... a simple application that continually displays the size of its main window, even when the window is resized Use properties 1 Start Visual Studio 2010 if it is not already running 2 Open the WindowProperties project, located in the \Microsoft Press \Visual CSharp Step By Step\ Chapter 15\WindowProperties folder in your Documents folder ... write a destructor to clean up the resources used by an object when memory is recycled by the garbage collector You have also seen how to use the using statement to implement exception-safe disposal of resources n If you want to continue to the next chapter Keep Visual Studio 2010 running, and turn to Chapter 15 n If you want to exit Visual Studio 2010 now On the File menu, click Exit If you see a... Windows, and Button c ontrols by using the Properties window in Microsoft Visual Studio 2010, you are actually generating code that sets the values of these properties at run time Some components have a large number of properties, although some properties are more commonly used than o thers You can write your own code to modify many of these properties at run time by using the same syntax you have... properties by declaring set accessors n Create interfaces that declare properties n Implement interfaces containing properties by using structures and classes n Generate properties automatically based on field definitions n Use properties to initialize objects The first two parts of this book have introduced the core syntax of the C# language and have shown you how to use C# to build new types by using... written by the get and set accessors The next code example shows the ScreenPosition structure rewritten by using properties When reading this code, notice the following: n Lowercase x and y are private fields n Uppercase X and Y are public properties n All set accessors are passed the data to be written by using a hidden, built-in parameter named value Tip The fields and properties follow the standard Microsoft. .. Querying In-Memory Data by Using Query Expressions Operator Overloading 295 315 329 353 381 395 419 293 Chapter 15 Implementing Properties to Access Fields After completing this chapter, you will be able to: n Encapsulate logical fields by using properties n Control read access to properties by declaring get accessors n Control... you can’t call Finalize yourself Why Use the Garbage Collector? You should now understand that you can never destroy an object yourself by using C# code There just isn’t any syntax to do it The runtime does it for you, and there are good reasons why the designers of C# decided to prevent you from doing it If it were your responsibility to destroy objects, sooner or later one of the following situations . Drawing project in Visual Studio. Note A finished working copy of the previous exercise is available in the Drawing project located in the Microsoft Press Visual CSharp Step By Step Chapter 13Drawing. is not already running. 2. Open the UsingStatement project, located in the Microsoft Press Visual CSharp Step By Step Chapter 14UsingStatement folder in your Documents folder. . file will be bypassed. You will rewrite the code to use a using statement instead, ensuring that the code is exception safe. Write a using statement 1. Start Microsoft Visual Studio 2010 if it