C# 2005 Programmer’s Reference - chapter 14 docx

105 281 0
C# 2005 Programmer’s Reference - chapter 14 docx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

288 CHAPTER 14 Implementing Abstract Classes and Interfaces This chapter shows how to create both abstract classes and interfaces. You’ll see how both can help create interfaces for other code to use. Another important point is that you’ll see how to write code that implements interfaces. Abstract Classes Abstract classes are a special type of base classes. In addition to normal class members, there are abstract class members. These abstract class members are methods and properties declared without an implementation. All classes derived directly from abstract classes must implement these abstract methods and properties. You can look at an abstract class as a cross between normal classes and interfaces, in that they can have their own implementa- tion yet define an interface through abstract members that derived classes must imple- ment. C# interface types are covered later in this chapter, but for right now you need to know that interfaces do not contain any implementation, a fact that differentiates them from abstract classes that can have implementation. Abstract classes can never be instantiated. This would be illogical, because of the members without implementations. So what good is a class that can’t be instantiated? Lots! Abstract classes sit toward the top of a class hierarchy. They establish structure and meaning to code. They make frameworks easier to build. This is possible because abstract classes have information and behavior common to all derived classes in a framework. Take a look at the following example: abstract public class Contact { protected string name; public Contact() { // statements } public abstract void GenerateReport(); abstract public string Name { get; set; } } public class Customer : Contact { string gender; decimal income; 289 Abstract Classes int numberOfVisits; public Customer() { // statements } public override void GenerateReport() { // unique report } public override string Name { get { numberOfVisits++; return name; } set { name = value; numberOfVisits = 0; } } } public class SiteOwner : Contact { int siteHits; string mySite; public SiteOwner() { // statements } public override void GenerateReport() { // unique report } public override string Name { get { 14 290 CHAPTER 14 Implementing Abstract Classes and Interfaces siteHits++; return name; } set { name = value; siteHits = 0; } } } This example has three classes. The first class, Contact, is now an abstract class. This is shown as the first modifier of its class declaration. Contact has two abstract members, which includes an abstract method named GenerateReport. This method is declared with the abstract modifier in front of the method declaration. It has no implementation (no braces) and is terminated with a semicolon. The Name property is also declared abstract. The accessors of properties are terminated with semicolons. The abstract base class Contact has two derived classes: Customer and SiteOwner. Both of these derived classes implement the abstract members of the Contact class. The GenerateReport method in each derived class has an override modifier in its declara- tion. Likewise, the Name declaration contains an override modifier in both Customer and SiteOwner. The override modifier for the overridden GenerateReport method and Name property is mandatory. C# requires explicit declaration of intent when overriding methods. This feature promotes safe code by avoiding the accidental overriding of base class methods, which is what actually does happen in other languages. Leaving out the override modi- fier generates an error. Similarly, adding a new modifier also generates an error. Abstract methods are implicitly virtual. They must be overridden and cannot be hidden, which the new modifier or the lack of a modifier would be trying to do. Notice the name field in the Contact class. It has a protected modifier. Remember, a protected modifier allows derived classes to access base class members. In this case, it enables the overridden Name property to access the name field in the Contact class. Abstract Class and Interface Differences Interfaces are specifications defining the type of behaviors a class must implement. They are contracts a class uses to allow other classes to interact with it in a well-defined and anticipated manner. Interfaces define an explicit definition of how classes should interact. Abstract classes are a unit of abstraction, whereas interfaces define further specification. Abstract class members may contain implementations. The exception is when an abstract class member has an abstract modifier. Derived classes must implement abstract class members with an abstract class modifier, but they don’t have to implement any other 291 Defining Interface Types method declared virtual. On the other hand, classes inheriting an interface must imple- ment every interface member. Interface members have no implementation. Implementing Interfaces As stated in the preceding section, a C# interface defines a contract for all custom class and struct objects that implement that interface. This means that you must write code in the class or struct that implements the interface, to provide the implementation for the interface. Instead of just exposing public members, the interface is enforced by the C# compiler, and you must define the members specified in the interface (contract). First, I show you how to define an interface and then follow up with explanations of how to implement interface members. Later, you see how to use the interface. Defining Interface Types The definition of an interface is much like a class or struct. However, because an interface doesn’t have an implementation, its members are defined differently. The following example shows how to declare an interface and how an interface is structured: [modifiers] interface IName [: Interface [, Interfaces]] { // methods // properties // indexers // events } The modifiers can be public or internal, just like other type definitions. Next is the keyword interface followed by the interface name, IName, which is simply an identifier. A common convention is to make the first character of an interface name the uppercase letter I. After the name is a colon and a comma-separated list of interfaces that this one inherits. The colon/inherited interface list is optional. Although classes have only single class inheritance, they have multiple interface inheri- tance. Along the same lines, structs and interfaces have multiple interface inheritance. Later sections of this chapter explain some of the issues involved with multiple interface inheritance. Following the interface inheritance list is the interface body, which consists of the members enclosed in curly braces. Legal members are methods, properties, indexers, and events. Interface members are assumed to be public and, therefore, have no modifiers. Interface implementations must also be public. 14 292 CHAPTER 14 Implementing Abstract Classes and Interfaces The following sections explain how to define interface members for the following inter- face, IBroker: public interface IBroker { // interface members } Remember, interface members don’t have implementations. It is the class or struct that implements the interface. Those implementations, by whatever classes or structs you code, must have members that match what the specified interface members. Methods Interface methods are defined similarly to normal methods. The difference is that they have no implementation. The following example shows an interface method: string GetRating(string stock, out string provider) ; Everything is the same as a normal method, except that a semicolon is used at the end in place of the implementation code block. Properties At first, it might seem strange that a property could be an interface member, especially when the normal implementation of a property is associated with a field. Although fields can’t be interface members, this doesn’t prevent the use of properties, because the imple- mentation of a property is independent of its specification. Remember, one of the primary reasons for properties is to encapsulate implementation. Therefore, the fact that an interface doesn’t have fields is not a limiting factor. For this reason, property specifications may be added to interfaces with no problem, as shown in the following example: decimal PricePerTrade { get ; set ; } This property example is structured similarly to a regular property. However, the accessors don’t have implementations. Instead, the get and set keywords are closed with a semi- colon. 293 Implicit Implementation Indexers Interface indexer specifications appear similar to normal indexers, but their accessor speci- fications are the same as property accessors, as shown in the following example: decimal this[string StockName] { get ; set ; } This example shows an indexer accepting a string argument and returning a decimal value. Its get and set accessors are closed with semicolons, similar to how property acces- sors are defined. Events There is no difference in the way interface events and normal events are declared. Here’s an example: public delegate void ChangeRegistrar(object sender, object evnt); event ChangeRegistrar PriceChange ; As you can see, the event has the exact same type of signature that goes in a normal class. No surprises. Implicit Implementation It is easy to implement a single interface on a class or struct. It just requires declaration of the class or struct with interface inheritance and the implementation of those interface members. There are two views of implicit interface implementation. The first is the easiest: a single class implementing a single interface. The second uses interface polymorphism by implementing the same interface in two separate classes. Single Class Interface Implementation As mentioned previously, it is easy for a class to implement a single interface. Implement- ation of an interface simply follows the rules set in previous sections. The interface in Listing 14.1 represents a plausible set of class members that a stockbroker or financial company may want to expose to a client. LISTING 14.1 The IBroker Interface Definition using System; using System.Collections; 14 294 CHAPTER 14 Implementing Abstract Classes and Interfaces LISTING 14.1 Continued public delegate void ChangeRegistrar(object sender, object evnt); public interface IBroker { string GetRating(string stock) ; decimal PricePerTrade { get ; set ; } decimal this[string StockName] { get ; set ; } event ChangeRegistrar PriceChange ; } The interface name, from Listing 14.1, begins with the conventional I in IBroker. It has four members: the GetRating method, the PricePerTrade property, an indexer, and the PriceChange event. The delegate type of the PriceChange property, ChangeRegistrar, is defined, too. As mentioned earlier, interface members do not have implementations. It is up to a class or struct to implement interface member declarations. Listing 14.2 shows a class that implements the IBroker interface, which is an object that represents a finance company. It has an overridden constructor to ensure that the pricePerTrade field is initialized properly. LISTING 14.2 An Implementation of the IBroker Interface public class FinanceCompany : IBroker { Dictionary<string, decimal> m_stocks = new Dictionary<string, decimal>(); decimal pricePerTrade; public FinanceCompany() : this(10.50m) { } 295 Implicit Implementation LISTING 14.2 Continued public FinanceCompany(decimal price) { pricePerTrade = price; } public string GetRating(string stock) { return “Buy”; } public decimal PricePerTrade { get { return pricePerTrade; } set { pricePerTrade = value; PriceChange(“FinanceBroker”, value); } } public decimal this[string StockName] { get { return m_stocks[StockName]; } set { m_stocks.Add(StockName, value); } } public event ChangeRegistrar PriceChange; } As the listing shows, the private pricePerTrade field is encapsulated by the PricePerTrade property. The get accessor of the PricePerTrade property simply returns the current value of the pricePerTrade field. However, the set accessor provides more functionality. After setting the new value of the pricePerTrade field, it invokes the PriceChange event. The PriceChange event is based on the ChangeRegistrar delegate, which specifies two object parameters. When the PriceChange event is invoked in the set accessor of the 14 296 CHAPTER 14 Implementing Abstract Classes and Interfaces PricePerTrade property, it receives two arguments. The string argument is implicitly converted to object. The decimal value is boxed and passed as an object. Event declaration and implementation are normally as simple as shown in Listing 14.2. However, the event implementation can be much more sophisticated if there is a need to override its add and remove accessors. The GetRating method is implemented to always return the same value. In this context, the broker is always bullish, regardless of the real value of a stock. The indexer implementation uses the Dictionary<string, decimal> collection for main- taining its data. This is a generic collection where the key is type string and the value is type decimal. You’ll learn more about generics in Chapter 17, “Parameterizing Type with Generics and Writing Iterators.” The indexer’s get accessor returns the value of a stock using Stockname as a key. The set accessor creates a new Hashtable entry by using the indexer string parameter as a key and the value passed in as the hash value. Now there’s a class that faithfully follows the contract of the IBroker interface. What’s good about this is that any program can now use that class and automatically know that it has specific class members that can be used in a specific way. Listing 14.3 shows a program that uses a class that implements the IBroker interface. LISTING 14.3 Implementation of Single Interface Inheritance public class InterfaceTester { public static int Main() { FinanceCompany finco = new FinanceCompany(); InterfaceTester iftst = new InterfaceTester(); finco.PriceChange += new ChangeRegistrar( iftst.PricePerTradeChange); finco[“ABC”] = 15.39m; finco[“DEF”] = 37.51m; Console.WriteLine(“ABC Price is {0}”, finco[“ABC”]); Console.WriteLine(“DEF Price is {0}”, finco[“DEF”]); Console.WriteLine(““); finco.PricePerTrade = 10.55m; Console.WriteLine(““); string recommendation = finco.GetRating(“ABC”); Console.WriteLine( 297 Implicit Implementation LISTING 14.3 Continued “finco’s recommendation for ABC is {0}”, recommendation); return 0; } public void PricePerTradeChange(object sender, object evnt) { Console.WriteLine( “Trading price for {0} changed to {1}.”, (string) sender, (decimal) evnt); } } And here’s the output from Listing 14.3: ABC Price is 15.39 DEF Price is 37.51 Trading price for FinanceBroker changed to 10.55. finco’s recommendation for ABC is Buy Because the FinanceCompany class implements the IBroker interface, the program in Listing 14.3 knows what class members it can implement. The Main method instantiates a FinanceCompany class, finco, and an InterfaceTester class, iftst. The InterfaceTester class has an event handler method named PricePerTradeChange. In the Main method, the InterfaceTester class makes itself a subscriber to the finco.PriceChange event by assigning the PricePerTradeChange event handler to that event. Next, two stocks are added to finco. This is done by using a stock name as the indexer and giving it a decimal value. The assignment is verified with a couple Console.WriteLine methods. The finco object’s PricePerTrade property is changed to 10.55m. Within the FinanceCompany class, this invokes the PriceChange event, which calls the PricePerTrade method of the InterfaceTester class. This shows how events are effective tools for obtaining status changes in an object. Finally, the GetRating method of the finco object is invoked. Method calls are the most typical interface members. The output follows the sequence of events in Main. The first two lines show the stock values from the indexer. The third line is from the PricePerTradeChange event handler in the InterfaceTester class. The last line of output shows the results of requesting a stock rating from the finco object. 14 [...]... intentionally left blank PART 3 Applying Advanced C# Language Features CHAPTER 15 Managing Object Lifetime CHAPTER 16 Declaring Attributes and Examining Code with Reflection CHAPTER 17 Parameterizing Type with Generics and Writing Iterators CHAPTER 18 Using Lambdas and Expression Trees This page intentionally left blank CHAPTER 15 Managing Object Lifetime IN THIS CHAPTER Object Initialization Object Initializers... conversion is required to reference the explicit interface member definition It generally indicates that the interface is not of particular interest to a user of that class or struct Listing 14. 7 demonstrates explicit interface implementation 14 public interface IAdvisor { string GetRating(string stock) ; 306 CHAPTER 14 Implementing Abstract Classes and Interfaces LISTING 14. 7 Explicit Interface Implementation... public string GetRating(string stock) { Stock myStock = m_stocks[stock]; return Enum.GetName(typeof(StockRating), myStock.Rating); } 14 public decimal Price { get { return price; } set { price = value; } } 300 CHAPTER 14 Implementing Abstract Classes and Interfaces LISTING 14. 4 Continued private StockRating AssignRating(Stock newStock) { Random myRand = new Random(); int nextRating = myRand.Next(4); return... AssignRating(myStock); m_stocks.Add(StockName, myStock); } } public event ChangeRegistrar PriceChange; } 14 public decimal PricePerTrade { get { return pricePerTrade; } set { pricePerTrade = value; PriceChange(brokerName, value); } } 308 CHAPTER 14 Implementing Abstract Classes and Interfaces Listing 14. 7 shows how to use explicit interface implementation to disambiguate the implementation of interface... from the interface This follows object-oriented principles where a derived class “is” an inherited class Remember, when declaring inheritance relationships with both a class and interfaces, the class comes first in the list Using the IBroker and IAdvisor interfaces from previous sections in this chapter, Listing 14. 9 shows an example of how this could occur LISTING 14. 9 Interface Mapping Example public... this[string StockName] { get { Stock myStock = (Stock)stocks[StockName]; return myStock.Price; } 14 public string GetRating(string stock) { Stock myStock = (Stock) stocks[stock]; return Enum.GetName(typeof(StockRating), myStock.Rating); } 312 CHAPTER 14 Implementing Abstract Classes and Interfaces LISTING 14. 9 Continued set { Stock myStock myStock.Name myStock.Price myStock.Rating = = = = new Stock();... public event ChangeRegistrar PriceChange; } Listing 14. 9 is the same as the StockBroker class in previous sections of this chapter with one significant exception: It doesn’t derive from the IBroker interface If a class were to derive from the StockBroker class in Listing 14. 9, it would inherit the entire implementation The following example shows how the C# interface mapping strategy works to find the... Dictionary m_stocks = new Dictionary(); decimal pricePerTrade; public FinanceCompany() : this(10.50m) { } public FinanceCompany(decimal price) { 314 CHAPTER 14 Implementing Abstract Classes and Interfaces LISTING 14. 10 Continued pricePerTrade = price; } public string GetRating(string stock) { return “Buy”; } public decimal PricePerTrade { get { return pricePerTrade; } set { pricePerTrade... objects, regardless of the IBroker-derived object’s underlying implementation Then, the PricePerTrade property of each broker is updated This triggers each broker’s PriceChange event and invokes the PricePerTradeChange event handler of the InterfaceTester class After that, the GetRating method is called This is more 14 Broker’s recommendation for ABC is Buy 304 CHAPTER 14 Implementing Abstract Classes... learning about constructors Interacting with the Garbage Collector 320 CHAPTER 15 Managing Object Lifetime Object Initialization You’ve been implicitly using constructors in all the C# programs written so far Every object in C# has a constructor, whether default or defined When you don’t define explicit constructors for an object, C# creates a default constructor behind the scenes This section shows . to a client. LISTING 14. 1 The IBroker Interface Definition using System; using System.Collections; 14 294 CHAPTER 14 Implementing Abstract Classes and Interfaces LISTING 14. 1 Continued public. must also be public. 14 292 CHAPTER 14 Implementing Abstract Classes and Interfaces The following sections explain how to define interface members for the following inter- face, IBroker: public. have their own implementa- tion yet define an interface through abstract members that derived classes must imple- ment. C# interface types are covered later in this chapter, but for right now

Ngày đăng: 12/08/2014, 09:23

Mục lục

    Part 2 Object-Oriented Programming with C#

    14 Implementing Abstract Classes and Interfaces

    Abstract Class and Interface Differences

    Part 3 Applying Advanced C# Language Features

    Interacting with the Garbage Collector

    16 Declaring Attributes and Examining Code with Reflection

    Creating Your Own Attributes

    17 Parameterizing Type with Generics and Writing Iterators

    Understanding the Benefits of Generics

Tài liệu cùng người dùng

  • Đang cập nhật ...