{"id":298,"date":"2023-10-20T13:19:53","date_gmt":"2023-10-20T05:19:53","guid":{"rendered":"https:\/\/miie.net\/?p=298"},"modified":"2023-10-20T13:19:53","modified_gmt":"2023-10-20T05:19:53","slug":"pro-c10-chapter-6-understanding-inheritance-and-polymorphism","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=298","title":{"rendered":"Pro C#10 CHAPTER 6 Understanding Inheritance and Polymorphism"},"content":{"rendered":"<h1>CHAPTER 6 Understanding Inheritance and Polymorphism<\/h1>\n<p>\u7b2c6\u7ae0 \u4e86\u89e3\u7ee7\u627f\u548c\u591a\u6001\u6027<\/p>\n<p>Chapter 5 examined the first pillar of OOP: encapsulation. At that time, you learned how to build a single well-defined class type with constructors and various members (fields, properties, methods, constants, and read-only fields). This chapter will focus on the remaining two pillars of OOP: inheritance and polymorphism.<br \/>\n\u7b2c5\u7ae0\u63a2\u8ba8\u4e86OOP\u7684\u7b2c\u4e00\u4e2a\u652f\u67f1\uff1a\u5c01\u88c5\u3002\u5f53\u65f6\uff0c\u60a8\u5b66\u4e60\u4e86\u5982\u4f55\u4f7f\u7528\u6784\u9020\u51fd\u6570\u548c\u5404\u79cd\u6210\u5458\uff08\u5b57\u6bb5\u3001\u5c5e\u6027\u3001\u65b9\u6cd5\u3001\u5e38\u91cf\u548c\u53ea\u8bfb\u5b57\u6bb5\uff09\u751f\u6210\u5355\u4e2a\u5b9a\u4e49\u826f\u597d\u7684\u7c7b\u7c7b\u578b\u3002\u672c\u7ae0\u5c06\u91cd\u70b9\u4ecb\u7ecd OOP \u7684\u5176\u4f59\u4e24\u4e2a\u652f\u67f1\uff1a\u7ee7\u627f\u548c\u591a\u6001\u6027\u3002<\/p>\n<p>First, you will learn how to build families of related classes using inheritance. As you will see, this form of code reuse allows you to define common functionality in a parent class that can be leveraged, and possibly altered, by child classes. Along the way, you will learn how to establish a polymorphic interface into class hierarchies using virtual and abstract members, as well as the role of explicit casting.<br \/>\n\u9996\u5148\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528\u7ee7\u627f\u6784\u5efa\u76f8\u5173\u7c7b\u7684\u65cf\u3002\u5982\u60a8\u6240\u89c1\uff0c\u8fd9\u79cd\u5f62\u5f0f\u7684\u4ee3\u7801\u91cd\u7528\u5141\u8bb8\u60a8\u5728\u7236\u7c7b\u4e2d\u5b9a\u4e49\u53ef\u4ee5\u5229\u7528\u7684\u901a\u7528\u529f\u80fd\uff0c\u5e76\u4e14\u53ef\u80fd\u88ab\u5b50\u7c7b\u6539\u53d8\u3002\u5728\u6b64\u8fc7\u7a0b\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528\u865a\u62df\u548c\u62bd\u8c61\u6210\u5458\u5728\u7c7b\u5c42\u6b21\u7ed3\u6784\u4e2d\u5efa\u7acb\u591a\u6001\u63a5\u53e3\uff0c\u4ee5\u53ca\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u7684\u4f5c\u7528\u3002<\/p>\n<p>The chapter will wrap up by examining the role of the ultimate parent class in the .NET base class libraries: System.Object.<br \/>\n\u672c\u7ae0\u5c06\u901a\u8fc7\u68c0\u67e5 .NET \u57fa\u7c7b\u5e93\u4e2d\u6700\u7ec8\u7236\u7c7b\u7684\u89d2\u8272\u6765\u7ed3\u675f\uff1aSystem.Object\u3002<\/p>\n<h2>Understanding the Basic Mechanics of Inheritance<\/h2>\n<p>\u4e86\u89e3\u7ee7\u627f\u7684\u57fa\u672c\u673a\u5236<\/p>\n<p>Recall from Chapter 5 that inheritance is an aspect of OOP that facilitates code reuse. Specifically speaking, code reuse comes in two flavors: inheritance (the \u201cis-a\u201d relationship) and the containment\/delegation model (the \u201chas-a\u201d relationship). Let\u2019s begin this chapter by examining the classical inheritance model of the \u201cis-a\u201d relationship.<br \/>\n\u56de\u60f3\u4e00\u4e0b\u7b2c 5 \u7ae0\uff0c\u7ee7\u627f\u662f OOP \u7684\u4e00\u4e2a\u65b9\u9762\uff0c\u5b83\u6709\u52a9\u4e8e\u4ee3\u7801\u91cd\u7528\u3002\u5177\u4f53\u6765\u8bf4\uff0c\u4ee3\u7801\u91cd\u7528\u6709\u4e24\u79cd\u5f62\u5f0f\uff1a\u7ee7\u627f\uff08\u201cis-a\u201d\u5173\u7cfb\uff09\u548c\u5305\u542b\/\u59d4\u6d3e\u6a21\u578b\uff08\u201chas-a\u201d\u5173\u7cfb\uff09\u3002\u8ba9\u6211\u4eec\u4ece\u68c0\u67e5\u201cis-a\u201d\u5173\u7cfb\u7684\u7ecf\u5178\u7ee7\u627f\u6a21\u578b\u5f00\u59cb\u672c\u7ae0\u3002<\/p>\n<p>When you establish \u201cis-a\u201d relationships between classes, you are building a dependency between two or more class types. The basic idea behind classical inheritance is that new classes can be created using existing classes as a starting point. To begin with a simple example, create a new Console Application project named BasicInheritance. Now assume you have designed a class named Car that models some basic details of an automobile.<br \/>\n\u5728\u7c7b\u4e4b\u95f4\u5efa\u7acb\u201cis-a\u201d\u5173\u7cfb\u65f6\uff0c\u662f\u5728\u4e24\u4e2a\u6216\u591a\u4e2a\u7c7b\u7c7b\u578b\u4e4b\u95f4\u5efa\u7acb\u4f9d\u8d56\u5173\u7cfb\u3002\u7ecf\u5178\u7ee7\u627f\u80cc\u540e\u7684\u57fa\u672c\u601d\u60f3\u662f\u53ef\u4ee5\u4f7f\u7528\u73b0\u6709\u7c7b\u4f5c\u4e3a\u8d77\u70b9\u521b\u5efa\u65b0\u7c7b\u3002\u82e5\u8981\u4ece\u4e00\u4e2a\u7b80\u5355\u7684\u793a\u4f8b\u5f00\u59cb\uff0c\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a BasicInheritance \u7684\u65b0\u63a7\u5236\u53f0\u5e94\u7528\u7a0b\u5e8f\u9879\u76ee\u3002\u73b0\u5728\u5047\u8bbe\u60a8\u8bbe\u8ba1\u4e86\u4e00\u4e2a\u540d\u4e3a Car \u7684\u7c7b\uff0c\u7528\u4e8e\u5bf9\u6c7d\u8f66\u7684\u4e00\u4e9b\u57fa\u672c\u7ec6\u8282\u8fdb\u884c\u5efa\u6a21\u3002<\/p>\n<p>namespace BasicInheritance;<br \/>\n\/\/ A simple base class. class Car<br \/>\n{<br \/>\npublic readonly int MaxSpeed; private int _currSpeed;<\/p>\n<p>public Car(int max)<br \/>\n{<br \/>\nMaxSpeed = max;<br \/>\n}<\/p>\n<p>public Car()<br \/>\n{<br \/>\nMaxSpeed = 55;<br \/>\n}<br \/>\npublic int Speed<br \/>\n{<br \/>\nget { return _currSpeed; } set<br \/>\n{<br \/>\n_currSpeed = value;<br \/>\nif (_currSpeed &gt; MaxSpeed)<br \/>\n{<br \/>\n_currSpeed = MaxSpeed;<br \/>\n}<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>Notice that the Car class is using encapsulation services to control access to the private currSpeed field using a public property named Speed. At this point, you can exercise your Car type as follows:<br \/>\n\u8bf7\u6ce8\u610f\uff0cCar \u7c7b\u4f7f\u7528\u5c01\u88c5\u670d\u52a1\u6765\u63a7\u5236\u5bf9\u79c1\u6709 currSpeed \u5b57\u6bb5\u7684\u8bbf\u95ee\uff0c\u8be5\u5c5e\u6027\u4f7f\u7528\u540d\u4e3a Speed \u7684\u516c\u5171\u5c5e\u6027\u3002\u6b64\u65f6\uff0c\u60a8\u53ef\u4ee5\u6309\u5982\u4e0b\u65b9\u5f0f\u953b\u70bc\u60a8\u7684\u6c7d\u8f66\u7c7b\u578b\uff1a<\/p>\n<p>using BasicInheritance;<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Basic Inheritance <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n\/\/ Make a Car object, set max speed and current speed. Car myCar = new Car(80) {Speed = 50};<\/p>\n<p>\/\/ Print current speed.<br \/>\nConsole.WriteLine(&quot;My car is going {0} MPH&quot;, myCar.Speed); Console.ReadLine();<\/p>\n<h2>Specifying the Parent Class of an Existing Class<\/h2>\n<p>\u6307\u5b9a\u73b0\u6709\u7c7b\u7684\u7236\u7c7b<\/p>\n<p>Now assume you want to build a new class named MiniVan. Like a basic Car, you want to define the MiniVan class to support data for a maximum speed, a current speed, and a property named Speed to allow the object user to modify the object\u2019s state. Clearly, the Car and MiniVan classes are related; in fact, it can be said that a MiniVan \u201cis-a\u201d type of Car. The \u201cis-a\u201d relationship (formally termed classical inheritance) allows you to build new class definitions that extend the functionality of an existing class.<br \/>\n\u73b0\u5728\u5047\u8bbe\u60a8\u8981\u6784\u5efa\u4e00\u4e2a\u540d\u4e3a MiniVan \u7684\u65b0\u7c7b\u3002\u4e0e\u57fa\u672c Car \u4e00\u6837\uff0c\u60a8\u5e0c\u671b\u5b9a\u4e49 MiniVan \u7c7b\u4ee5\u652f\u6301\u6700\u5927\u901f\u5ea6\u3001\u5f53\u524d\u901f\u5ea6\u548c\u540d\u4e3a Speed \u7684\u5c5e\u6027\u7684\u6570\u636e\uff0c\u4ee5\u5141\u8bb8\u5bf9\u8c61\u7528\u6237\u4fee\u6539\u5bf9\u8c61\u7684\u72b6\u6001\u3002\u663e\u7136\uff0c\u6c7d\u8f66\u548c\u5c0f\u578b\u8d27\u8f66\u7c7b\u522b\u662f\u76f8\u5173\u7684;\u4e8b\u5b9e\u4e0a\uff0c\u53ef\u4ee5\u8bf4\u5c0f\u578b\u8d27\u8f66\u201c\u662f\u4e00\u79cd\u201d\u7c7b\u578b\u7684\u6c7d\u8f66\u3002\u201cis-a\u201d\u5173\u7cfb\uff08\u6b63\u5f0f\u79f0\u4e3a\u7ecf\u5178\u7ee7\u627f\uff09\u5141\u8bb8\u60a8\u6784\u5efa\u6269\u5c55\u73b0\u6709\u7c7b\u529f\u80fd\u7684\u65b0\u7c7b\u5b9a\u4e49\u3002<\/p>\n<p>The existing class that will serve as the basis for the new class is termed a base class, superclass, or parent class. The role of a base class is to define all the common data and members for the classes that extend it.<br \/>\n\u5c06\u7528\u4f5c\u65b0\u7c7b\u57fa\u7840\u7684\u73b0\u6709\u7c7b\u79f0\u4e3a\u57fa\u7c7b\u3001\u8d85\u7c7b\u6216\u7236\u7c7b\u3002\u57fa\u7c7b\u7684\u4f5c\u7528\u662f\u4e3a\u6269\u5c55\u57fa\u7c7b\u7684\u7c7b\u5b9a\u4e49\u6240\u6709\u516c\u5171\u6570\u636e\u548c\u6210\u5458\u3002<\/p>\n<p>The extending classes are formally termed derived or child classes. In C#, you make use of the colon operator on the class definition to establish an \u201cis-a\u201d relationship between classes. Assume you have authored the following new MiniVan class:<br \/>\n\u6269\u5c55\u7c7b\u6b63\u5f0f\u79f0\u4e3a\u6d3e\u751f\u7c7b\u6216\u5b50\u7c7b\u3002\u5728 C# \u4e2d\uff0c\u4f7f\u7528\u7c7b\u5b9a\u4e49\u4e0a\u7684\u5192\u53f7\u8fd0\u7b97\u7b26\u5728\u7c7b\u4e4b\u95f4\u5efa\u7acb\u201cis-a\u201d\u5173\u7cfb\u3002\u5047\u8bbe\u60a8\u5df2\u7ecf\u521b\u4f5c\u4e86\u4ee5\u4e0b\u65b0\u7684\u5c0f\u578b\u8d27\u8f66\u7c7b\uff1a<\/p>\n<p>namespace BasicInheritance;<br \/>\n\/\/ MiniVan &quot;is-a&quot; Car. class MiniVan : Car<br \/>\n{<br \/>\n}<\/p>\n<p>Currently, this new class has not defined any members whatsoever. So, what have you gained by extending your MiniVan from the Car base class? Simply put, MiniVan objects now have access to each public member defined within the parent class.<br \/>\n\u76ee\u524d\uff0c\u8fd9\u4e2a\u65b0\u7c7b\u5c1a\u672a\u5b9a\u4e49\u4efb\u4f55\u6210\u5458\u3002\u90a3\u4e48\uff0c\u901a\u8fc7\u4ece\u6c7d\u8f66\u57fa\u7c7b\u6269\u5c55\u60a8\u7684\u5c0f\u578b\u8d27\u8f66\uff0c\u60a8\u83b7\u5f97\u4e86\u4ec0\u4e48\uff1f\u7b80\u800c\u8a00\u4e4b\uff0cMiniVan \u5bf9\u8c61\u73b0\u5728\u53ef\u4ee5\u8bbf\u95ee\u7236\u7c7b\u4e2d\u5b9a\u4e49\u7684\u6bcf\u4e2a\u516c\u5171\u6210\u5458\u3002<\/p>\n<p>\u25a0Note Although constructors are typically defined as public, a derived class never inherits the constructors of a parent class. Constructors are used to construct only the class that they are defined within, although they can be called by a derived class through constructor chaining. This will be covered shortly.<br \/>\n\u6ce8\u610f \u5c3d\u7ba1\u6784\u9020\u51fd\u6570\u901a\u5e38\u5b9a\u4e49\u4e3a\u516c\u5171\u51fd\u6570\uff0c\u4f46\u6d3e\u751f\u7c7b\u4ece\u4e0d\u7ee7\u627f\u7236\u7c7b\u7684\u6784\u9020\u51fd\u6570\u3002 \u6784\u9020\u51fd\u6570\u4ec5\u7528\u4e8e\u6784\u9020\u5728\u5176\u4e2d\u5b9a\u4e49\u5b83\u4eec\u7684\u7c7b\uff0c\u5c3d\u7ba1\u6d3e\u751f\u7c7b\u53ef\u4ee5\u901a\u8fc7\u6784\u9020\u51fd\u6570\u94fe\u63a5\u8c03\u7528\u5b83\u4eec\u3002\u8fd9\u5c06\u5f88\u5feb\u4ecb\u7ecd\u3002<\/p>\n<p>Given the relation between these two class types, you can now make use of the MiniVan class like so:<br \/>\n\u7ed9\u5b9a\u8fd9\u4e24\u79cd\u7c7b\u7c7b\u578b\u4e4b\u95f4\u7684\u5173\u7cfb\uff0c\u60a8\u73b0\u5728\u53ef\u4ee5\u50cf\u8fd9\u6837\u4f7f\u7528 MiniVan \u7c7b\uff1a<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Basic Inheritance <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n.<br \/>\n\/\/ Now make a MiniVan object.<br \/>\nMiniVan myVan = new MiniVan {Speed = 10}; Console.WriteLine(&quot;My van is going {0} MPH&quot;, myVan.Speed); Console.ReadLine();<\/p>\n<p>Again, notice that although you have not added any members to the MiniVan class, you have direct access to the public Speed property of your parent class and have thus reused code. This is a far better approach than creating a MiniVan class that has the same members as Car, such as a Speed property. If you did duplicate code between these two classes, you would need to now maintain two bodies of code, which is certainly a poor use of your time.<br \/>\n\u540c\u6837\uff0c\u8bf7\u6ce8\u610f\uff0c\u5c3d\u7ba1\u60a8\u5c1a\u672a\u5411 MiniVan \u7c7b\u6dfb\u52a0\u4efb\u4f55\u6210\u5458\uff0c\u4f46\u60a8\u53ef\u4ee5\u76f4\u63a5\u8bbf\u95ee\u7236\u7c7b\u7684\u516c\u5171 Speed \u5c5e\u6027\uff0c\u56e0\u6b64\u91cd\u7528\u4e86\u4ee3\u7801\u3002\u8fd9\u6bd4\u521b\u5efa\u5177\u6709\u4e0e Car \u76f8\u540c\u6210\u5458\u7684 MiniVan \u7c7b\uff08\u5982 Speed \u5c5e\u6027\uff09\u8981\u597d\u5f97\u591a\u3002\u5982\u679c\u4f60\u5728\u8fd9\u4e24\u4e2a\u7c7b\u4e4b\u95f4\u590d\u5236\u4e86\u4ee3\u7801\uff0c\u4f60\u73b0\u5728\u9700\u8981\u7ef4\u62a4\u4e24\u4e2a\u4ee3\u7801\u4f53\uff0c\u8fd9\u80af\u5b9a\u662f\u5bf9\u4f60\u7684\u65f6\u95f4\u7684\u4e0d\u826f\u5229\u7528\u3002<\/p>\n<p>Always remember that inheritance preserves encapsulation; therefore, the following code results in a compiler error, as private members can never be accessed from an object reference:<br \/>\n\u6c38\u8fdc\u8bb0\u4f4f\uff0c\u7ee7\u627f\u4fdd\u7559\u5c01\u88c5;\u56e0\u6b64\uff0c\u4ee5\u4e0b\u4ee3\u7801\u4f1a\u5bfc\u81f4\u7f16\u8bd1\u5668\u9519\u8bef\uff0c\u56e0\u4e3a\u6c38\u8fdc\u65e0\u6cd5\u4ece\u5bf9\u8c61\u5f15\u7528\u8bbf\u95ee\u79c1\u6709\u6210\u5458\uff1a<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Basic Inheritance <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\n\/\/ Make a MiniVan object. MiniVan myVan = new MiniVan(); myVan.Speed = 10;<br \/>\nConsole.WriteLine(&quot;My van is going {0} MPH&quot;, myVan.Speed);<br \/>\n\/\/ Error! Can't access private members! myVan._currSpeed = 55; Console.ReadLine();<\/p>\n<p>On a related note, if the MiniVan defined its own set of members, it would still not be able to access any private member of the Car base class. Remember, private members can be accessed only by the class that defines it. For example, the following method in MiniVan would result in a compiler error:<br \/>\n\u5728\u76f8\u5173\u7684\u8bf4\u660e\u4e2d\uff0c\u5982\u679cMiniVan\u5b9a\u4e49\u4e86\u81ea\u5df1\u7684\u6210\u5458\u96c6\uff0c\u5b83\u4ecd\u7136\u65e0\u6cd5\u8bbf\u95eeCar\u57fa\u7c7b\u7684\u4efb\u4f55\u79c1\u6709\u6210\u5458\u3002\u8bf7\u8bb0\u4f4f\uff0c\u79c1\u6709\u6210\u5458\u53ea\u80fd\u7531\u5b9a\u4e49\u5b83\u7684\u7c7b\u8bbf\u95ee\u3002\u4f8b\u5982\uff0cMiniVan \u4e2d\u7684\u4ee5\u4e0b\u65b9\u6cd5\u5c06\u5bfc\u81f4\u7f16\u8bd1\u5668\u9519\u8bef\uff1a<\/p>\n<p>\/\/ MiniVan derives from Car. class MiniVan : Car<br \/>\n{<br \/>\npublic void TestMethod()<br \/>\n{<br \/>\n\/\/ OK! Can access public members<br \/>\n\/\/ of a parent within a derived type. Speed = 10;<\/p>\n<p>\/\/ Error! Cannot access private<br \/>\n\/\/ members of parent within a derived type.<br \/>\n_currSpeed = 10;<br \/>\n}<br \/>\n}<\/p>\n<h2>Regarding Multiple Base Classes<\/h2>\n<p>\u5173\u4e8e\u591a\u4e2a\u57fa\u7c7b<\/p>\n<p>Speaking of base classes, it is important to keep in mind that C# demands that a given class have exactly one direct base class. It is not possible to create a class type that directly derives from two or more base classes (this technique, which is supported in unmanaged C++, is known as multiple inheritance, or simply MI). If you attempted to create a class that specifies two direct parent classes, as shown in the following code, you would receive compiler errors:<br \/>\n\u8bf4\u5230\u57fa\u7c7b\uff0c\u91cd\u8981\u7684\u662f\u8981\u8bb0\u4f4f\uff0cC# \u8981\u6c42\u7ed9\u5b9a\u7684\u7c7b\u53ea\u6709\u4e00\u4e2a\u76f4\u63a5\u57fa\u7c7b\u3002\u4e0d\u53ef\u80fd\u521b\u5efa\u76f4\u63a5\u6d3e\u751f\u81ea\u4e24\u4e2a\u6216\u591a\u4e2a\u57fa\u7c7b\u7684\u7c7b\u7c7b\u578b\uff08\u975e\u6258\u7ba1C++\u652f\u6301\u6b64\u6280\u672f\u79f0\u4e3a\u591a\u91cd\u7ee7\u627f\uff0c\u6216\u7b80\u79f0\u4e3a MI\uff09\u3002\u5982\u679c\u5c1d\u8bd5\u521b\u5efa\u6307\u5b9a\u4e24\u4e2a\u76f4\u63a5\u7236\u7c7b\u7684\u7c7b\uff08\u5982\u4ee5\u4e0b\u4ee3\u7801\u6240\u793a\uff09\uff0c\u5219\u4f1a\u6536\u5230\u7f16\u8bd1\u5668\u9519\u8bef\uff1a<\/p>\n<p>\/\/ Illegal! C# does not allow<br \/>\n\/\/ multiple inheritance for classes! class WontWork<br \/>\n: BaseClassOne, BaseClassTwo<br \/>\n{}<\/p>\n<p>As you will see in Chapter 8, the .NET Core platform does allow a given class, or structure, to implement any number of discrete interfaces. In this way, a C# type can exhibit a number of behaviors while avoiding the complexities associated with MI. Using this technique, you can build sophisticated interface hierarchies that model complex behaviors (again, see Chapter 8).<br \/>\n\u6b63\u5982\u60a8\u5c06\u5728\u7b2c 8 \u7ae0\u4e2d\u770b\u5230\u7684\uff0c.NET Core \u5e73\u53f0\u786e\u5b9e\u5141\u8bb8\u7ed9\u5b9a\u7684\u7c7b\u6216\u7ed3\u6784\u5b9e\u73b0\u4efb\u610f\u6570\u91cf\u7684\u79bb\u6563\u63a5\u53e3\u3002\u8fd9\u6837\uff0cC# \u7c7b\u578b\u53ef\u4ee5\u8868\u73b0\u51fa\u8bb8\u591a\u884c\u4e3a\uff0c\u540c\u65f6\u907f\u514d\u4e0e MI \u76f8\u5173\u7684\u590d\u6742\u6027\u3002 \u4f7f\u7528\u6b64\u6280\u672f\uff0c\u60a8\u53ef\u4ee5\u6784\u5efa\u590d\u6742\u7684\u63a5\u53e3\u5c42\u6b21\u7ed3\u6784\u6765\u5bf9\u590d\u6742\u884c\u4e3a\u8fdb\u884c\u5efa\u6a21\uff08\u540c\u6837\uff0c\u8bf7\u53c2\u9605\u7b2c 8 \u7ae0\uff09\u3002<\/p>\n<h2>Using the sealed Keyword<\/h2>\n<p>\u4f7f\u7528\u5bc6\u5c01\u7684\u5173\u952e\u5b57<\/p>\n<p>C# supplies another keyword, sealed, that prevents inheritance from occurring. When you mark a class as sealed, the compiler will not allow you to derive from this type. For example, assume you have decided that it makes no sense to further extend the MiniVan class.<br \/>\nC# \u63d0\u4f9b\u4e86\u53e6\u4e00\u4e2a\u5173\u952e\u5b57 Seal \u6765\u9632\u6b62\u53d1\u751f\u7ee7\u627f\u3002\u5c06\u7c7b\u6807\u8bb0\u4e3a\u5bc6\u5c01\u65f6\uff0c\u7f16\u8bd1\u5668\u5c06\u4e0d\u5141\u8bb8\u4ece\u6b64\u7c7b\u578b\u6d3e\u751f\u3002\u4f8b\u5982\uff0c\u5047\u8bbe\u60a8\u5df2\u7ecf\u51b3\u5b9a\u8fdb\u4e00\u6b65\u6269\u5c55 MiniVan \u7c7b\u662f\u6ca1\u6709\u610f\u4e49\u7684\u3002<\/p>\n<p>\/\/ The MiniVan class cannot be extended! sealed class MiniVan : Car<br \/>\n{<br \/>\n}<\/p>\n<p>If you (or a teammate) were to attempt to derive from this class, you would receive a compile-time error.<br \/>\n\u5982\u679c\u60a8\uff08\u6216\u56e2\u961f\u6210\u5458\uff09\u5c1d\u8bd5\u4ece\u6b64\u7c7b\u6d3e\u751f\uff0c\u60a8\u5c06\u6536\u5230\u7f16\u8bd1\u65f6\u9519\u8bef\u3002<\/p>\n<p>\/\/ Error! Cannot extend<br \/>\n\/\/ a class marked with the sealed keyword! class DeluxeMiniVan<br \/>\n: MiniVan<br \/>\n{<br \/>\n}<\/p>\n<p>Most often, sealing a class makes the best sense when you are designing a utility class. For example, the System namespace defines numerous sealed classes, such as the String class. Thus, just like the MiniVan, if you attempt to build a new class that extends System.String, you will receive a compile-time error.<br \/>\n\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u5728\u8bbe\u8ba1\u5b9e\u7528\u7a0b\u5e8f\u7c7b\u65f6\uff0c\u5bc6\u5c01\u7c7b\u6700\u6709\u610f\u4e49\u3002\u4f8b\u5982\uff0cSystem \u547d\u540d\u7a7a\u95f4\u5b9a\u4e49\u4e86\u8bb8\u591a\u5bc6\u5c01\u7c7b\uff0c\u5982 String \u7c7b\u3002\u56e0\u6b64\uff0c\u5c31\u50cfMiniVan\u4e00\u6837\uff0c\u5982\u679c\u60a8\u5c1d\u8bd5\u6784\u5efa\u6269\u5c55System.String\u7684\u65b0\u7c7b\uff0c\u60a8\u5c06\u6536\u5230\u7f16\u8bd1\u65f6\u9519\u8bef\u3002<\/p>\n<p>\/\/ Another error! Cannot extend<br \/>\n\/\/ a class marked as sealed! class MyString<br \/>\n: String<br \/>\n{<br \/>\n}<\/p>\n<p>\u25a0Note  In Chapter 4, you learned that C# structures are always implicitly sealed (see Table 4-3). Therefore, you can never derive one structure from another structure, a class from a structure, or a structure from a class. Structures can be used to model only stand-alone, atomic, user-defined data types. If you want to leverage the \u201cis-a\u201d relationship, you must use classes.<br \/>\n\u6ce8\u610f \u5728\u7b2c 4 \u7ae0\u4e2d\uff0c\u60a8\u4e86\u89e3\u5230 C# \u7ed3\u6784\u59cb\u7ec8\u662f\u9690\u5f0f\u5bc6\u5c01\u7684\uff08\u8bf7\u53c2\u9605\u8868 4-3\uff09\u3002\u56e0\u6b64\uff0c\u60a8\u6c38\u8fdc\u4e0d\u80fd\u4ece\u53e6\u4e00\u4e2a\u7ed3\u6784\u6d3e\u751f\u4e00\u4e2a\u7ed3\u6784\uff0c\u4ece\u7ed3\u6784\u6d3e\u751f\u4e00\u4e2a\u7c7b\uff0c\u6216\u4ece\u7c7b\u6d3e\u751f\u4e00\u4e2a\u7ed3\u6784\u3002\u7ed3\u6784\u53ea\u80fd\u7528\u4e8e\u5bf9\u72ec\u7acb\u7684\u3001\u539f\u5b50\u7684\u3001\u7528\u6237\u5b9a\u4e49\u7684\u6570\u636e\u7c7b\u578b\u8fdb\u884c\u5efa\u6a21\u3002\u5982\u679c\u8981\u5229\u7528\u201cis-a\u201d\u5173\u7cfb\uff0c\u5219\u5fc5\u987b\u4f7f\u7528\u7c7b\u3002<\/p>\n<p>As you would guess, there are many more details to inheritance that you will come to know during the remainder of this chapter. For now, simply keep in mind that the colon operator allows you to establish base\/derived class relationships, while the sealed keyword prevents subsequent inheritance from occurring.<br \/>\n\u6b63\u5982\u60a8\u6240\u731c\u5230\u7684\uff0c\u5728\u672c\u7ae0\u7684\u5176\u4f59\u90e8\u5206\uff0c\u60a8\u5c06\u4e86\u89e3\u66f4\u591a\u6709\u5173\u7ee7\u627f\u7684\u7ec6\u8282\u3002\u73b0\u5728\uff0c\u53ea\u9700\u8bb0\u4f4f\u5192\u53f7\u8fd0\u7b97\u7b26\u5141\u8bb8\u60a8\u5efa\u7acb\u57fa\/\u6d3e\u751f\u7c7b\u5173\u7cfb\uff0c\u800c sealed \u5173\u952e\u5b57\u53ef\u9632\u6b62\u53d1\u751f\u540e\u7eed\u7ee7\u627f\u3002<\/p>\n<h2>Revisiting Visual Studio Class Diagrams<\/h2>\n<p>\u91cd\u65b0\u8bbf\u95ee Visual Studio \u7c7b\u56fe<\/p>\n<p>In Chapter 2, I briefly mentioned that Visual Studio allows you to establish base\/derived class relationships visually at design time. To leverage this aspect of the IDE, your first step is to include a new class diagram file into your current project. To do so, access the Project \u27a4 Add New Item menu option and click the Class Diagram icon (in Figure 6-1, I renamed the file from ClassDiagram1.cd to Cars.cd).<br \/>\n\u5728\u7b2c2\u7ae0\u4e2d\uff0c\u6211\u7b80\u8981\u5730\u63d0\u5230Visual Studio\u5141\u8bb8\u60a8\u5728\u8bbe\u8ba1\u65f6\u76f4\u89c2\u5730\u5efa\u7acb\u57fa\/\u6d3e\u751f\u7c7b\u5173\u7cfb\u3002\u82e5\u8981\u5229\u7528 IDE \u7684\u8fd9\u4e00\u65b9\u9762\uff0c\u7b2c\u4e00\u6b65\u662f\u5728\u5f53\u524d\u9879\u76ee\u4e2d\u5305\u542b\u4e00\u4e2a\u65b0\u7684\u7c7b\u56fe\u6587\u4ef6\u3002\u4e3a\u6b64\uff0c\u8bf7\u8bbf\u95ee\u201c\u9879\u76ee\u201d\u27a4\u201c\u6dfb\u52a0\u65b0\u9879\u201d\u83dc\u5355\u9009\u9879\uff0c\u7136\u540e\u5355\u51fb\u201c\u7c7b\u56fe\u201d\u56fe\u6807\uff08\u5728\u56fe 6-1 \u4e2d\uff0c\u6211\u5c06\u6587\u4ef6\u4ece ClassDiagram1.cd \u91cd\u547d\u540d\u4e3a Cars.cd\uff09\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/0601.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Figure 6-1. Inserting a new class diagram<br \/>\n\u56fe 6-1\u3002 \u63d2\u5165\u65b0\u7684\u7c7b\u56fe<\/p>\n<p>After you click the Add button, you will be presented with a blank designer surface. To add types to a class designer, simply drag each file from the Solution Explorer window onto the surface. Also recall that if you delete an item from the visual designer (simply by selecting it and pressing the Delete key), this will not destroy the associated source code but simply remove the item off the designer surface. Figure 6-2 shows the current class hierarchy.<br \/>\n\u5355\u51fb\u201c\u6dfb\u52a0\u201d\u6309\u94ae\u540e\uff0c\u5c06\u663e\u793a\u4e00\u4e2a\u7a7a\u767d\u7684\u8bbe\u8ba1\u5668\u56fe\u9762\u3002\u82e5\u8981\u5411\u7c7b\u8bbe\u8ba1\u5668\u6dfb\u52a0\u7c7b\u578b\uff0c\u53ea\u9700\u5c06\u6bcf\u4e2a\u6587\u4ef6\u4ece\u201c\u89e3\u51b3\u65b9\u6848\u8d44\u6e90\u7ba1\u7406\u5668\u201d\u7a97\u53e3\u62d6\u5230\u56fe\u9762\u4e0a\u5373\u53ef\u3002\u53e6\u8bf7\u6ce8\u610f\uff0c\u5982\u679c\u4ece\u53ef\u89c6\u5316\u8bbe\u8ba1\u5668\u4e2d\u5220\u9664\u67d0\u4e2a\u9879\uff08\u53ea\u9700\u9009\u62e9\u8be5\u9879\u5e76\u6309 Delete \u952e\uff09\uff0c\u8fd9\u4e0d\u4f1a\u7834\u574f\u5173\u8054\u7684\u6e90\u4ee3\u7801\uff0c\u800c\u53ea\u662f\u4ece\u8bbe\u8ba1\u5668\u56fe\u9762\u4e2d\u5220\u9664\u8be5\u9879\u3002\u56fe 6-2 \u663e\u793a\u4e86\u5f53\u524d\u7684\u7c7b\u5c42\u6b21\u7ed3\u6784\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/0602.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Figure 6-2. The visual class designer of Visual Studio<br \/>\n\u56fe 6-2\u3002 Visual Studio \u7684\u89c6\u89c9\u7c7b\u8bbe\u8ba1\u5668<\/p>\n<p>Beyond simply displaying the relationships of the types within your current application, recall from Chapter 2 that you can also create new types and populate their members using the Class Designer toolbox and Class Details window.<br \/>\n\u9664\u4e86\u7b80\u5355\u5730\u663e\u793a\u5f53\u524d\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7c7b\u578b\u7684\u5173\u7cfb\u4e4b\u5916\uff0c\u8fd8\u8bb0\u5f97\u7b2c 2 \u7ae0\u4e2d\u8fd8\u53ef\u4ee5\u4f7f\u7528\u201c\u7c7b\u8bbe\u8ba1\u5668\u201d\u5de5\u5177\u7bb1\u548c\u201c\u7c7b\u8be6\u7ec6\u4fe1\u606f\u201d\u7a97\u53e3\u521b\u5efa\u65b0\u7c7b\u578b\u5e76\u586b\u5145\u5176\u6210\u5458\u3002<\/p>\n<p>If you want to make use of these visual tools during the remainder of the book, feel free. However, always make sure you analyze the generated code so you have a solid understanding of what these tools have done on your behalf.<br \/>\n\u5982\u679c\u60a8\u60f3\u5728\u672c\u4e66\u7684\u5176\u4f59\u90e8\u5206\u4f7f\u7528\u8fd9\u4e9b\u53ef\u89c6\u5316\u5de5\u5177\uff0c\u8bf7\u968f\u610f\u3002\u4f46\u662f\uff0c\u8bf7\u59cb\u7ec8\u786e\u4fdd\u5206\u6790\u751f\u6210\u7684\u4ee3\u7801\uff0c\u4ee5\u4fbf\u5bf9\u8fd9\u4e9b\u5de5\u5177\u4ee3\u8868\u60a8\u6267\u884c\u7684\u64cd\u4f5c\u6709\u6df1\u5165\u7684\u4e86\u89e3\u3002<\/p>\n<h2>Understanding the Second Pillar of OOP: The Details of Inheritance<\/h2>\n<p>\u4e86\u89e3 OOP \u7684\u7b2c\u4e8c\u4e2a\u652f\u67f1\uff1a\u7ee7\u627f\u7684\u7ec6\u8282<\/p>\n<p>Now that you have seen the basic syntax of inheritance, let\u2019s create a more complex example and get to know the numerous details of building class hierarchies. To do so, you will be reusing the Employee class you designed in Chapter 5. To begin, create a new C# Console Application project named Employees.<br \/>\n\u73b0\u5728\u60a8\u5df2\u7ecf\u4e86\u89e3\u4e86\u7ee7\u627f\u7684\u57fa\u672c\u8bed\u6cd5\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u4e00\u4e2a\u66f4\u590d\u6742\u7684\u793a\u4f8b\uff0c\u5e76\u4e86\u89e3\u6784\u5efa\u7c7b\u5c42\u6b21\u7ed3\u6784\u7684\u4f17\u591a\u7ec6\u8282\u3002\u4e3a\u6b64\uff0c\u60a8\u5c06\u91cd\u7528\u5728\u7b2c 5 \u7ae0\u4e2d\u8bbe\u8ba1\u7684 Employee \u7c7b\u3002\u9996\u5148\uff0c\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a\u201c\u5458\u5de5\u201d\u7684\u65b0 C# \u63a7\u5236\u53f0\u5e94\u7528\u7a0b\u5e8f\u9879\u76ee\u3002<\/p>\n<p>Next, copy the Employee.cs, Employee.Core.cs, and EmployeePayTypeEnum.cs files you created in the EmployeeApp example from Chapter 5 into the Employees project.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u5c06\u60a8\u5728\u7b2c 5 \u7ae0\u7684 EmployeeApp \u793a\u4f8b\u4e2d\u521b\u5efa\u7684 Employee.cs\u3001Employee.Core.cs \u548c EmployeePayTypeEnum.cs \u6587\u4ef6\u590d\u5236\u5230 Employees \u9879\u76ee\u4e2d\u3002<\/p>\n<p>\u25a0Note  Prior to .NET Core, the files needed to be referenced in the .csproj file to use them in a C# project. With .NET Core, all the files in the current directory structure are automatically included in your project. Simply copying the two files from the other project into the current project directory is enough to have them included in your project.<br \/>\n\u6ce8\u610f \u5728 .NET Core \u4e4b\u524d\uff0c\u9700\u8981\u5728 .csproj \u6587\u4ef6\u4e2d\u5f15\u7528\u8fd9\u4e9b\u6587\u4ef6\u624d\u80fd\u5728 C# \u9879\u76ee\u4e2d\u4f7f\u7528\u5b83\u4eec\u3002\u4f7f\u7528 .NET Core\uff0c\u5f53\u524d\u76ee\u5f55\u7ed3\u6784\u4e2d\u7684\u6240\u6709\u6587\u4ef6\u90fd\u4f1a\u81ea\u52a8\u5305\u542b\u5728\u9879\u76ee\u4e2d\u3002\u53ea\u9700\u5c06\u4e24\u4e2a\u6587\u4ef6\u4ece\u53e6\u4e00\u4e2a\u9879\u76ee\u590d\u5236\u5230\u5f53\u524d\u9879\u76ee\u76ee\u5f55\u4e2d\u5c31\u8db3\u4ee5\u5c06\u5b83\u4eec\u5305\u542b\u5728\u9879\u76ee\u4e2d\u3002<\/p>\n<p>Before you start to build some derived classes, you have two details to attend to. Because the original Employee class was created in a project named EmployeeApp, the class has been wrapped within an identically named .NET Core namespace. Chapter 16 will examine namespaces in detail; however, for simplicity, rename the current namespace (in all three file locations) to Employees to match your new project name.<br \/>\n\u5728\u5f00\u59cb\u6784\u5efa\u4e00\u4e9b\u6d3e\u751f\u7c7b\u4e4b\u524d\uff0c\u6709\u4e24\u4e2a\u7ec6\u8282\u9700\u8981\u6ce8\u610f\u3002\u7531\u4e8e\u539f\u59cb Employee \u7c7b\u662f\u5728\u540d\u4e3a EmployeeApp \u7684\u9879\u76ee\u4e2d\u521b\u5efa\u7684\uff0c\u56e0\u6b64\u8be5\u7c7b\u5df2\u5305\u88c5\u5728\u540d\u79f0\u76f8\u540c\u7684 .NET Core \u547d\u540d\u7a7a\u95f4\u4e2d\u3002\u7b2c16\u7ae0\u5c06\u8be6\u7ec6\u68c0\u67e5\u547d\u540d\u7a7a\u95f4;\u4f46\u662f\uff0c\u4e3a\u7b80\u5355\u8d77\u89c1\uff0c\u8bf7\u5c06\u5f53\u524d\u547d\u540d\u7a7a\u95f4\uff08\u5728\u6240\u6709\u4e09\u4e2a\u6587\u4ef6\u4f4d\u7f6e\u4e2d\uff09\u91cd\u547d\u540d\u4e3a\u201c\u5458\u5de5\u201d\u4ee5\u5339\u914d\u65b0\u9879\u76ee\u540d\u79f0\u3002<\/p>\n<p>\/\/ Be sure to change the namespace name in both C# files! namespace Employees;<br \/>\npartial class Employee<br \/>\n{...}<\/p>\n<p>\u25a0Note  If you removed the default constructor during the changes to the Employee class in Chapter 5, make sure to add it back into the class.<br \/>\n\u6ce8\u610f \u5982\u679c\u5728\u7b2c 5 \u7ae0\u4e2d\u5bf9 Employee \u7c7b\u8fdb\u884c\u66f4\u6539\u671f\u95f4\u5220\u9664\u4e86\u9ed8\u8ba4\u6784\u9020\u51fd\u6570\uff0c\u8bf7\u786e\u4fdd\u5c06\u5176\u6dfb\u52a0\u56de\u7c7b\u4e2d\u3002<\/p>\n<p>The second detail is to remove any of the commented code from the different iterations of the Employee<br \/>\nclass from the Chapter 5 example.<br \/>\n\u7b2c\u4e8c\u4e2a\u7ec6\u8282\u662f\u4ece\u5458\u5de5\u7684\u4e0d\u540c\u8fed\u4ee3\u4e2d\u5220\u9664\u4efb\u4f55\u6ce8\u91ca\u4ee3\u7801\u7b2c 5 \u7ae0\u793a\u4f8b\u4e2d\u7684\u7c7b \u3002<\/p>\n<p>\u25a0Note As a sanity check, compile and run your new project by entering dotnet run in a command prompt (in your project\u2019s directory) or pressing Ctrl+F5 if you are using Visual Studio. The program will not do anything at this point; however, this will ensure you do not have any compiler errors.<br \/>\n\u6ce8\u610f \u4f5c\u4e3a\u5065\u5168\u6027\u68c0\u67e5\uff0c\u901a\u8fc7\u5728\u547d\u4ee4\u63d0\u793a\u7b26\u4e0b\u8f93\u5165 dotnet run\uff08\u5728\u9879\u76ee\u76ee\u5f55\u4e2d\uff09\u6216\u6309 Ctrl+F5\uff08\u5982\u679c\u4f7f\u7528 Visual Studio\uff09\u6765\u7f16\u8bd1\u548c\u8fd0\u884c\u65b0\u9879\u76ee\u3002\u6b64\u65f6\u7a0b\u5e8f\u4e0d\u4f1a\u6267\u884c\u4efb\u4f55\u64cd\u4f5c;\u4f46\u662f\uff0c\u8fd9\u5c06\u786e\u4fdd\u60a8\u6ca1\u6709\u4efb\u4f55\u7f16\u8bd1\u5668\u9519\u8bef\u3002<\/p>\n<p>Your goal is to create a family of classes that model various types of employees in a company. Assume you want to leverage the functionality of the Employee class to create two new classes (SalesPerson and Manager). The new SalesPerson class \u201cis-an\u201d Employee (as is a Manager). Remember that under the classical inheritance model, base classes (such as Employee) are used to define general characteristics that are common to all descendants. Subclasses (such as SalesPerson and Manager) extend this general functionality while adding more specific functionality.<br \/>\n\u60a8\u7684\u76ee\u6807\u662f\u521b\u5efa\u4e00\u4e2a\u7c7b\u7cfb\u5217\uff0c\u5bf9\u516c\u53f8\u4e2d\u7684\u5404\u79cd\u7c7b\u578b\u7684\u5458\u5de5\u8fdb\u884c\u5efa\u6a21\u3002\u5047\u8bbe\u60a8\u8981\u5229\u7528 Employee \u7c7b\u7684\u529f\u80fd\u6765\u521b\u5efa\u4e24\u4e2a\u65b0\u7c7b\uff08\u9500\u552e\u4eba\u5458\u548c\u7ecf\u7406\uff09\u3002\u65b0\u7684\u9500\u552e\u4eba\u5458\u7c7b\u201c\u662f\u201d\u5458\u5de5\uff08\u7ecf\u7406\u4e5f\u662f\u5982\u6b64\uff09\u3002\u8bf7\u8bb0\u4f4f\uff0c\u5728\u7ecf\u5178\u7ee7\u627f\u6a21\u578b\u4e0b\uff0c\u57fa\u7c7b\uff08\u5982 Employee\uff09\u7528\u4e8e\u5b9a\u4e49\u6240\u6709\u540e\u4ee3\u5171\u6709\u7684\u4e00\u822c\u7279\u5f81\u3002\u5b50\u7c7b\uff08\u5982\u9500\u552e\u4eba\u5458\u548c\u7ecf\u7406\uff09\u6269\u5c55\u4e86\u6b64\u5e38\u89c4\u529f\u80fd\uff0c\u540c\u65f6\u6dfb\u52a0\u4e86\u66f4\u5177\u4f53\u7684\u529f\u80fd\u3002<\/p>\n<p>For your example, you will assume that the Manager class extends Employee by recording the number of stock options, while the SalesPerson class maintains the number of sales made. Insert a new class file (Manager.cs) that defines the Manager class with the following automatic property:<br \/>\n\u5bf9\u4e8e\u60a8\u7684\u793a\u4f8b\uff0c\u60a8\u5c06\u5047\u5b9a\u7ecf\u7406\u7c7b\u901a\u8fc7\u8bb0\u5f55\u80a1\u7968\u671f\u6743\u7684\u6570\u91cf\u6765\u6269\u5c55\u5458\u5de5\uff0c\u800c\u9500\u552e\u4eba\u5458\u7c7b\u5219\u7ef4\u62a4\u9500\u552e\u6570\u91cf\u3002\u63d2\u5165\u4e00\u4e2a\u65b0\u7684\u7c7b\u6587\u4ef6 \uff08Manager.cs\uff09\uff0c\u8be5\u6587\u4ef6\u4f7f\u7528\u4ee5\u4e0b\u81ea\u52a8\u5c5e\u6027\u5b9a\u4e49\u7ba1\u7406\u5668\u7c7b\uff1a<\/p>\n<p>namespace Employees;<br \/>\n\/\/ Managers need to know their number of stock options. class Manager : Employee<br \/>\n{<br \/>\npublic int StockOptions { get; set; }<br \/>\n}<\/p>\n<p>Next, add another new class file (SalesPerson.cs) that defines the SalesPerson class with a fitting automatic property.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6dfb\u52a0\u53e6\u4e00\u4e2a\u65b0\u7684\u7c7b\u6587\u4ef6 \uff08SalesPerson.cs\uff09\uff0c\u8be5\u6587\u4ef6\u4f7f\u7528\u5408\u9002\u7684\u81ea\u52a8\u5c5e\u6027\u5b9a\u4e49 SalesPerson \u7c7b\u3002<\/p>\n<p>namespace Employees;<br \/>\n\/\/ Salespeople need to know their number of sales. class SalesPerson : Employee<br \/>\n{<br \/>\npublic int SalesNumber { get; set; }<br \/>\n}<\/p>\n<p>Now that you have established an \u201cis-a\u201d relationship, SalesPerson and Manager have automatically inherited all public members of the Employee base class. To illustrate, update your top-level statements as follows:<br \/>\n\u73b0\u5728\uff0c\u60a8\u5df2\u7ecf\u5efa\u7acb\u4e86\u201cis-a\u201d\u5173\u7cfb\uff0c\u9500\u552e\u4eba\u5458\u548c\u7ecf\u7406\u5c06\u81ea\u52a8\u7ee7\u627f\u4e86\u5458\u5de5\u57fa\u7c7b\u7684\u6240\u6709\u516c\u5171\u6210\u5458\u3002\u4e3a\u4e86\u8bf4\u660e\u8fd9\u4e00\u70b9\uff0c\u8bf7\u6309\u5982\u4e0b\u6240\u793a\u66f4\u65b0\u9876\u7ea7\u8bed\u53e5\uff1a<\/p>\n<p>using Employees;<br \/>\n\/\/ Create a subclass object and access base class functionality. Console.WriteLine(&quot;<strong><strong><em> The Employee Class Hierarchy <\/em><\/strong><\/strong>\\n&quot;); SalesPerson fred = new SalesPerson<br \/>\n{<br \/>\nAge = 31, Name = &quot;Fred&quot;, SalesNumber = 50<br \/>\n};<\/p>\n<h2>Calling Base Class Constructors with the base Keyword<\/h2>\n<p>\u4f7f\u7528 base \u5173\u952e\u5b57\u8c03\u7528\u57fa\u7c7b\u6784\u9020\u51fd\u6570<\/p>\n<p>Currently, SalesPerson and Manager can be created only using the \u201cfreebie\u201d default constructor (see Chapter 5). With this in mind, assume you have added a new seven-argument constructor to the Manager type, which is invoked as follows:<br \/>\n\u76ee\u524d\uff0c\u53ea\u80fd\u4f7f\u7528\u201cfreebie\u201d\u9ed8\u8ba4\u6784\u9020\u51fd\u6570\u521b\u5efa\u9500\u552e\u4eba\u5458\u548c\u7ecf\u7406\uff08\u8bf7\u53c2\u9605\u7b2c 5 \u7ae0\uff09\u3002\u8003\u8651\u5230\u8fd9\u4e00\u70b9\uff0c\u5047\u8bbe\u60a8\u5df2\u5411 Manager \u7c7b\u578b\u6dfb\u52a0\u4e86\u4e00\u4e2a\u65b0\u7684\u4e03\u53c2\u6570\u6784\u9020\u51fd\u6570\uff0c\u8be5\u6784\u9020\u51fd\u6570\u7684\u8c03\u7528\u65b9\u5f0f\u5982\u4e0b\uff1a<\/p>\n<p>...<br \/>\n\/\/ Assume Manager has a constructor matching this signature:<br \/>\n\/\/ (string fullName, int age, int empId,<br \/>\n\/\/ float currPay, string ssn, int numbOfOpts)<br \/>\nManager chucky = new Manager(&quot;Chucky&quot;, 50, 92, 100000, &quot;333-23-2322&quot;, 9000);<\/p>\n<p>If you look at the parameter list, you can clearly see that most of these arguments should be stored in the member variables defined by the Employee base class. To do so, you might implement this custom constructor on the Manager class as follows:<br \/>\n\u5982\u679c\u60a8\u67e5\u770b\u53c2\u6570\u5217\u8868\uff0c\u60a8\u53ef\u4ee5\u6e05\u695a\u5730\u770b\u5230\u8fd9\u4e9b\u53c2\u6570\u4e2d\u7684\u5927\u591a\u6570\u5e94\u8be5\u5b58\u50a8\u5728 Employee \u57fa\u7c7b\u5b9a\u4e49\u7684\u6210\u5458\u53d8\u91cf\u4e2d\u3002\u4e3a\u6b64\uff0c\u53ef\u4ee5\u5728\u7ba1\u7406\u5668\u7c7b\u4e0a\u5b9e\u73b0\u6b64\u81ea\u5b9a\u4e49\u6784\u9020\u51fd\u6570\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>public Manager(string fullName, int age, int empId, float currPay, string ssn, int numbOfOpts)<br \/>\n{<br \/>\n\/\/ This property is defined by the Manager class. StockOptions = numbOfOpts;<\/p>\n<p>\/\/ Assign incoming parameters using the<br \/>\n\/\/ inherited properties of the parent class. Id = empId;<br \/>\nAge = age;<br \/>\nName = fullName;<br \/>\nPay = currPay;<br \/>\nPayType = EmployeePayTypeEnum.Salaried;<br \/>\n\/\/ OOPS! This would be a compiler error,<br \/>\n\/\/ if the SSN property were read-only! SocialSecurityNumber = ssn;<br \/>\n}<\/p>\n<p>The first issue with this approach is that if you defined any property as read-only (e.g., the SocialSecurityNumber property), you are unable to assign the incoming string parameter to this field, as shown in the final code statement of this custom constructor.<br \/>\n\u6b64\u65b9\u6cd5\u7684\u7b2c\u4e00\u4e2a\u95ee\u9898\u662f\uff0c\u5982\u679c\u5c06\u4efb\u4f55\u5c5e\u6027\u5b9a\u4e49\u4e3a\u53ea\u8bfb\uff08\u4f8b\u5982\uff0cSocialSecurityNumber \u5c5e\u6027\uff09\uff0c\u5219\u65e0\u6cd5\u5c06\u4f20\u5165\u7684\u5b57\u7b26\u4e32\u53c2\u6570\u5206\u914d\u7ed9\u6b64\u5b57\u6bb5\uff0c\u5982\u6b64\u81ea\u5b9a\u4e49\u6784\u9020\u51fd\u6570\u7684\u6700\u7ec8\u4ee3\u7801\u8bed\u53e5\u6240\u793a\u3002<\/p>\n<p>The second issue is that you have indirectly created a rather inefficient constructor, given that under C#, unless you say otherwise, the default constructor of a base class is called automatically before the logic of the derived constructor is executed. After this point, the current implementation accesses numerous public properties of the Employee base class to establish its state. Thus, you have really made eight hits (six inherited properties and two constructor calls) during the creation of a Manager object!<br \/>\n\u7b2c\u4e8c\u4e2a\u95ee\u9898\u662f\uff0c\u60a8\u95f4\u63a5\u521b\u5efa\u4e86\u4e00\u4e2a\u6548\u7387\u76f8\u5f53\u4f4e\u7684\u6784\u9020\u51fd\u6570\uff0c\u56e0\u4e3a\u5728 C# \u4e0b\uff0c\u9664\u975e\u60a8\u53e6\u6709\u8bf4\u660e\uff0c\u5426\u5219\u5728\u6267\u884c\u6d3e\u751f\u6784\u9020\u51fd\u6570\u7684\u903b\u8f91\u4e4b\u524d\u4f1a\u81ea\u52a8\u8c03\u7528\u57fa\u7c7b\u7684\u9ed8\u8ba4\u6784\u9020\u51fd\u6570\u3002\u5728\u6b64\u4e4b\u540e\uff0c\u5f53\u524d\u5b9e\u73b0\u5c06\u8bbf\u95ee Employee \u57fa\u7c7b\u7684\u5927\u91cf\u516c\u5171\u5c5e\u6027\u4ee5\u5efa\u7acb\u5176\u72b6\u6001\u3002\u56e0\u6b64\uff0c\u5728\u521b\u5efa Manager \u5bf9\u8c61\u671f\u95f4\uff0c\u60a8\u786e\u5b9e\u8fdb\u884c\u4e86 8 \u6b21\u547d\u4e2d\uff086 \u6b21\u7ee7\u627f\u5c5e\u6027\u548c 2 \u6b21\u6784\u9020\u51fd\u6570\u8c03\u7528\uff09\uff01<\/p>\n<p>To help optimize the creation of a derived class, you will do well to implement your subclass constructors to explicitly call an appropriate custom base class constructor, rather than the default. In this way, you are able to reduce the number of calls to inherited initialization members (which saves processing time). First, ensure your Employee parent class has the following six-argument constructor:<br \/>\n\u4e3a\u4e86\u5e2e\u52a9\u4f18\u5316\u6d3e\u751f\u7c7b\u7684\u521b\u5efa\uff0c\u6700\u597d\u5b9e\u73b0\u5b50\u7c7b\u6784\u9020\u51fd\u6570\uff0c\u4ee5\u663e\u5f0f\u8c03\u7528\u9002\u5f53\u7684\u81ea\u5b9a\u4e49\u57fa\u7c7b\u6784\u9020\u51fd\u6570\uff0c\u800c\u4e0d\u662f\u9ed8\u8ba4\u6784\u9020\u51fd\u6570\u3002\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u60a8\u53ef\u4ee5\u51cf\u5c11\u5bf9\u7ee7\u627f\u7684\u521d\u59cb\u5316\u6210\u5458\u7684\u8c03\u7528\u6b21\u6570\uff08\u4ece\u800c\u8282\u7701\u5904\u7406\u65f6\u95f4\uff09\u3002\u9996\u5148\uff0c\u786e\u4fdd Employee \u7236\u7c7b\u5177\u6709\u4ee5\u4e0b\u516d\u53c2\u6570\u6784\u9020\u51fd\u6570\uff1a<\/p>\n<p>\/\/ Add to the Employee base class.<br \/>\npublic Employee(string name, int age, int id, float pay, string empSsn, EmployeePay TypeEnum payType)<br \/>\n{<br \/>\nName = name;<br \/>\nId = id;<br \/>\nAge = age;<br \/>\nPay = pay;<br \/>\nSocialSecurityNumber = empSsn; PayType = payType;<br \/>\n}<\/p>\n<p>Now, let\u2019s retrofit the custom constructor of the Manager type to call this constructor using the base<br \/>\nkeyword.<\/p>\n<p>public Manager(string fullName, int age, int empId, float currPay, string ssn, int numbOfOpts)<br \/>\n: base(fullName, age, empId, currPay, ssn, EmployeePayTypeEnum.Salaried)<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u6539\u9020 Manager \u7c7b\u578b\u7684\u81ea\u5b9a\u4e49\u6784\u9020\u51fd\u6570\uff0c\u4ee5\u4f7f\u7528 base \u8c03\u7528\u6b64\u6784\u9020\u51fd\u6570\u5173\u952e\u8bcd\u3002<\/p>\n<p>{<br \/>\n\/\/ This property is defined by the Manager class. StockOptions = numbOfOpts;<br \/>\n}<\/p>\n<p>Here, the base keyword is hanging off the constructor signature (much like the syntax used to chain constructors on a single class using the this keyword, as was discussed in Chapter 5), which always indicates a derived constructor is passing data to the immediate parent constructor. In this situation, you are explicitly calling the six-parameter constructor defined by Employee and saving yourself unnecessary calls during the creation of the child class. Additionally, you added a specific behavior to the Manager class, in that the pay type is always set to Salaried. The custom SalesPerson constructor looks almost identical, with the exception that the pay type is set to Commission.<br \/>\n\u5728\u8fd9\u91cc\uff0cbase \u5173\u952e\u5b57\u6302\u5728\u6784\u9020\u51fd\u6570\u7b7e\u540d\u4e0a\uff08\u5f88\u50cf\u4f7f\u7528 this \u5173\u952e\u5b57\u5c06\u6784\u9020\u51fd\u6570\u94fe\u63a5\u5230\u5355\u4e2a\u7c7b\u4e0a\u7684\u8bed\u6cd5\uff0c\u5982\u7b2c 5 \u7ae0\u6240\u8ff0\uff09\uff0c\u8fd9\u59cb\u7ec8\u6307\u793a\u6d3e\u751f\u6784\u9020\u51fd\u6570\u6b63\u5728\u5c06\u6570\u636e\u4f20\u9012\u7ed9\u76f4\u63a5\u7236\u6784\u9020\u51fd\u6570\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u5c06\u663e\u5f0f\u8c03\u7528 Employee \u5b9a\u4e49\u7684\u516d\u53c2\u6570\u6784\u9020\u51fd\u6570\uff0c\u5e76\u5728\u521b\u5efa\u5b50\u7c7b\u671f\u95f4\u7701\u53bb\u4e0d\u5fc5\u8981\u7684\u8c03\u7528\u3002\u6b64\u5916\uff0c\u60a8\u8fd8\u5411\u7ecf\u7406\u7c7b\u6dfb\u52a0\u4e86\u7279\u5b9a\u884c\u4e3a\uff0c\u56e0\u4e3a\u4ed8\u85aa\u7c7b\u578b\u59cb\u7ec8\u8bbe\u7f6e\u4e3a\u201c\u53d7\u85aa\u201d\u3002\u81ea\u5b9a\u4e49\u9500\u552e\u4eba\u5458\u6784\u9020\u51fd\u6570\u770b\u8d77\u6765\u51e0\u4e4e\u76f8\u540c\uff0c\u53ea\u662f\u4ed8\u85aa\u7c7b\u578b\u8bbe\u7f6e\u4e3a\u4f63\u91d1\u3002<\/p>\n<p>\/\/ As a general rule, all subclasses should explicitly call an appropriate<br \/>\n\/\/ base class constructor.<br \/>\npublic SalesPerson(string fullName, int age, int empId, float currPay, string ssn, int numbOfSales)<br \/>\n: base(fullName, age, empId, currPay, ssn, EmployeePayTypeEnum.Commission)<br \/>\n{<br \/>\n\/\/ This belongs with us! SalesNumber = numbOfSales;<br \/>\n}<\/p>\n<p>\u25a0 Note You may use the base keyword whenever a subclass wants to access a public or protected member defined by a parent class. Use of this keyword is not limited to constructor logic. You will see examples using base in this manner during the examination of polymorphism, later in this chapter.<br \/>\n\u6ce8\u610f \u6bcf\u5f53\u5b50\u7c7b\u60f3\u8981\u8bbf\u95ee\u7531\u7236\u7c7b\u5b9a\u4e49\u7684\u516c\u5171\u6216\u53d7\u4fdd\u62a4\u6210\u5458\u65f6\uff0c\u90fd\u53ef\u4ee5\u4f7f\u7528 base \u5173\u952e\u5b57\u3002\u6b64\u5173\u952e\u5b57\u7684\u4f7f\u7528\u4e0d\u9650\u4e8e\u6784\u9020\u51fd\u6570\u903b\u8f91\u3002\u5728\u672c\u7ae0\u540e\u9762\u7684\u591a\u6001\u6027\u68c0\u67e5\u671f\u95f4\uff0c\u60a8\u5c06\u770b\u5230\u4ee5\u8fd9\u79cd\u65b9\u5f0f\u4f7f\u7528 base \u7684\u793a\u4f8b\u3002<\/p>\n<p>Finally, recall that once you add a custom constructor to a class definition, the default constructor is silently removed. Therefore, be sure to redefine the default constructor for the SalesPerson and Manager types. Here\u2019s an example:<br \/>\n\u6700\u540e\uff0c\u56de\u60f3\u4e00\u4e0b\uff0c\u5c06\u81ea\u5b9a\u4e49\u6784\u9020\u51fd\u6570\u6dfb\u52a0\u5230\u7c7b\u5b9a\u4e49\u540e\uff0c\u9ed8\u8ba4\u6784\u9020\u51fd\u6570\u5c06\u65e0\u63d0\u793a\u5220\u9664\u3002\u56e0\u6b64\uff0c\u8bf7\u786e\u4fdd\u91cd\u65b0\u5b9a\u4e49\u9500\u552e\u4eba\u5458\u548c\u7ecf\u7406\u7c7b\u578b\u7684\u9ed8\u8ba4\u6784\u9020\u51fd\u6570\u3002\u4e0b\u9762\u662f\u4e00\u4e2a\u793a\u4f8b\uff1a<\/p>\n<p>\/\/ Add back the default ctor<br \/>\n\/\/ in the Manager class as well. public SalesPerson() {}<\/p>\n<h2>Keeping Family Secrets: The protected Keyword<\/h2>\n<p>\u4fdd\u5b88\u5bb6\u5ead\u79d8\u5bc6\uff1a\u53d7\u4fdd\u62a4\u7684\u5173\u952e\u8bcd<\/p>\n<p>As you already know, public items are directly accessible from anywhere, while private items can be accessed only by the class that has defined them. Recall from Chapter 5 that C# takes the lead of many other modern object languages and provides an additional keyword to define member accessibility: protected.<br \/>\n\u5982\u60a8\u6240\u77e5\uff0c\u516c\u5171\u9879\u53ef\u4ee5\u4ece\u4efb\u4f55\u5730\u65b9\u76f4\u63a5\u8bbf\u95ee\uff0c\u800c\u79c1\u6709\u9879\u53ea\u80fd\u7531\u5b9a\u4e49\u5b83\u4eec\u7684\u7c7b\u8bbf\u95ee\u3002\u56de\u60f3\u4e00\u4e0b\u7b2c 5 \u7ae0\uff0c C# \u9886\u5148\u4e8e\u8bb8\u591a\u5176\u4ed6\u73b0\u4ee3\u5bf9\u8c61\u8bed\u8a00\uff0c\u5e76\u63d0\u4f9b\u4e86\u4e00\u4e2a\u989d\u5916\u7684\u5173\u952e\u5b57\u6765\u5b9a\u4e49\u6210\u5458\u53ef\u8bbf\u95ee\u6027\uff1a\u53d7\u4fdd\u62a4\u3002<\/p>\n<p>When a base class defines protected data or protected members, it establishes a set of items that can be accessed directly by any descendant. If you want to allow the SalesPerson and Manager child classes to directly access the data sector defined by Employee, you can update the original Employee class definition (in the EmployeeCore.cs file) as follows:<br \/>\n\u5f53\u57fa\u7c7b\u5b9a\u4e49\u53d7\u4fdd\u62a4\u7684\u6570\u636e\u6216\u53d7\u4fdd\u62a4\u7684\u6210\u5458\u65f6\uff0c\u5b83\u4f1a\u5efa\u7acb\u4e00\u7ec4\u53ef\u7531\u4efb\u4f55\u540e\u4ee3\u76f4\u63a5\u8bbf\u95ee\u7684\u9879\u3002\u5982\u679c\u8981\u5141\u8bb8\u201c\u9500\u552e\u4eba\u5458\u201d\u548c\u201c\u7ecf\u7406\u201d\u5b50\u7c7b\u76f4\u63a5\u8bbf\u95ee Employee \u5b9a\u4e49\u7684\u6570\u636e\u6247\u533a\uff0c\u53ef\u4ee5\u66f4\u65b0\u539f\u59cb Employee \u7c7b\u5b9a\u4e49\uff08\u5728 EmployeeCore.cs \u6587\u4ef6\u4e2d\uff09\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>\/\/ Protected state data. partial class Employee<\/p>\n<p>{<br \/>\n\/\/ Derived classes can now directly access this information. protected string EmpName;<br \/>\nprotected int EmpId; protected float CurrPay; protected int EmpAge; protected string EmpSsn;<br \/>\nprotected EmployeePayTypeEnum EmpPayType;...<br \/>\n}<\/p>\n<p>\u25a0 Note Convention is that protected members are named PascalCased (EmpName) and not underscore- camelCase (_empName). This is not a requirement of the language, but a common code style. If you decide to update the names as I have done here, make sure to rename all of the backing methods in your properties to match the PascalCased protected properties.<br \/>\n\u5f53\u57fa\u7c7b\u5b9a\u4e49\u53d7\u4fdd\u62a4\u7684\u6570\u636e\u6216\u53d7\u4fdd\u62a4\u7684\u6210\u5458\u65f6\uff0c\u5b83\u4f1a\u5efa\u7acb\u4e00\u7ec4\u53ef\u7531\u4efb\u4f55\u540e\u4ee3\u76f4\u63a5\u8bbf\u95ee\u7684\u9879\u3002\u5982\u679c\u8981\u5141\u8bb8\u201c\u9500\u552e\u4eba\u5458\u201d\u548c\u201c\u7ecf\u7406\u201d\u5b50\u7c7b\u76f4\u63a5\u8bbf\u95ee Employee \u5b9a\u4e49\u7684\u6570\u636e\u6247\u533a\uff0c\u53ef\u4ee5\u66f4\u65b0\u539f\u59cb Employee \u7c7b\u5b9a\u4e49\uff08\u5728 EmployeeCore.cs \u6587\u4ef6\u4e2d\uff09\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>The benefit of defining protected members in a base class is that derived types no longer have to access the data indirectly using public methods or properties. The possible downfall, of course, is that when a derived type has direct access to its parent\u2019s internal data, it is possible to accidentally bypass existing<br \/>\nbusiness rules found within public properties. When you define protected members, you are creating a level of trust between the parent class and the child class, as the compiler will not catch any violation of your type\u2019s business rules.<br \/>\n\u5728\u57fa\u7c7b\u4e2d\u5b9a\u4e49\u53d7\u4fdd\u62a4\u6210\u5458\u7684\u597d\u5904\u662f\uff0c\u6d3e\u751f\u7c7b\u578b\u4e0d\u518d\u9700\u8981\u4f7f\u7528\u516c\u5171\u65b9\u6cd5\u6216\u5c5e\u6027\u95f4\u63a5\u8bbf\u95ee\u6570\u636e\u3002\u5f53\u7136\uff0c\u53ef\u80fd\u7684\u5931\u8d25\u662f\uff0c\u5f53\u6d3e\u751f\u7c7b\u578b\u53ef\u4ee5\u76f4\u63a5\u8bbf\u95ee\u5176\u7236\u7ea7\u7684\u5185\u90e8\u6570\u636e\u65f6\uff0c\u53ef\u80fd\u4f1a\u610f\u5916\u5730\u7ed5\u8fc7\u73b0\u6709\u7684\u5728\u516c\u5171\u5c5e\u6027\u4e2d\u627e\u5230\u7684\u4e1a\u52a1\u89c4\u5219\u3002\u5b9a\u4e49\u53d7\u4fdd\u62a4\u7684\u6210\u5458\u65f6\uff0c\u5c06\u5728\u7236\u7c7b\u548c\u5b50\u7c7b\u4e4b\u95f4\u521b\u5efa\u4fe1\u4efb\u7ea7\u522b\uff0c\u56e0\u4e3a\u7f16\u8bd1\u5668\u4e0d\u4f1a\u6355\u83b7\u4efb\u4f55\u8fdd\u53cd\u7c7b\u578b\u4e1a\u52a1\u89c4\u5219\u7684\u884c\u4e3a\u3002<\/p>\n<p>Finally, understand that as far as the object user is concerned, protected data is regarded as private (as the user is \u201coutside\u201d the family). Therefore, the following is illegal:<br \/>\n\u6700\u540e\uff0c\u4e86\u89e3\u5c31\u5bf9\u8c61\u7528\u6237\u800c\u8a00\uff0c\u53d7\u4fdd\u62a4\u7684\u6570\u636e\u88ab\u89c6\u4e3a\u79c1\u6709\u6570\u636e\uff08\u56e0\u4e3a\u7528\u6237\u5728\u5bb6\u5ead\u201c\u4e4b\u5916\u201d\uff09\u3002\u56e0\u6b64\uff0c\u4ee5\u4e0b\u884c\u4e3a\u662f\u975e\u6cd5\u7684\uff1a<\/p>\n<p>\/\/ Error! Can't access protected data from client code. Employee emp = new Employee();<br \/>\nemp.empName = &quot;Fred&quot;;<\/p>\n<p>\u25a0 Note Although protected field data can break encapsulation, it is quite safe (and useful) to define protected methods. When building class hierarchies, it is common to define a set of methods that are only for use by derived types and are not intended for use by the outside world.<br \/>\n\u6ce8\u610f \u5c3d\u7ba1\u53d7\u4fdd\u62a4\u7684\u5b57\u6bb5\u6570\u636e\u53ef\u4ee5\u7834\u574f\u5c01\u88c5\uff0c\u4f46\u5b9a\u4e49\u53d7\u4fdd\u62a4\u7684\u65b9\u6cd5\u975e\u5e38\u5b89\u5168\uff08\u4e14\u6709\u7528\uff09\u3002\u5728\u751f\u6210\u7c7b\u5c42\u6b21\u7ed3\u6784\u65f6\uff0c\u901a\u5e38\u4f1a\u5b9a\u4e49\u4e00\u7ec4\u4ec5\u4f9b\u6d3e\u751f\u7c7b\u578b\u4f7f\u7528\u4e14\u4e0d\u4f9b\u5916\u90e8\u4e16\u754c\u4f7f\u7528\u7684\u65b9\u6cd5\u3002<\/p>\n<p>Adding a sealed Class<br \/>\n\u6dfb\u52a0\u5bc6\u5c01\u7c7b<\/p>\n<p>Recall that a sealed class cannot be extended by other classes. As mentioned, this technique is most often used when you are designing a utility class. However, when building class hierarchies, you might find that a certain branch in the inheritance chain should be \u201ccapped off,\u201d as it makes no sense to further extend the lineage. For example, assume you have added yet another class to your program (PtSalesPerson) that extends the existing SalesPerson type. Figure 6-3 shows the current update.<br \/>\n\u56de\u60f3\u4e00\u4e0b\uff0c\u5bc6\u5c01\u7c7b\u4e0d\u80fd\u7531\u5176\u4ed6\u7c7b\u6269\u5c55\u3002\u5982\u524d\u6240\u8ff0\uff0c\u6b64\u6280\u672f\u6700\u5e38\u7528\u4e8e\u8bbe\u8ba1\u5b9e\u7528\u7a0b\u5e8f\u7c7b\u3002\u4f46\u662f\uff0c\u5728\u6784\u5efa\u7c7b\u5c42\u6b21\u7ed3\u6784\u65f6\uff0c\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u7ee7\u627f\u94fe\u4e2d\u7684\u67d0\u4e2a\u5206\u652f\u5e94\u8be5\u88ab\u201c\u9650\u5236\u201d\uff0c\u56e0\u4e3a\u8fdb\u4e00\u6b65\u6269\u5c55\u4e16\u7cfb\u662f\u6ca1\u6709\u610f\u4e49\u7684\u3002\u4f8b\u5982\uff0c\u5047\u8bbe\u60a8\u5df2\u5411\u7a0b\u5e8f \uff08PtSalesPerson\uff09 \u6dfb\u52a0\u4e86\u53e6\u4e00\u4e2a\u6269\u5c55\u73b0\u6709 SalesPerson \u7c7b\u578b\u7684\u7c7b\u3002\u56fe 6-3 \u663e\u793a\u4e86\u5f53\u524d\u66f4\u65b0\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/0603.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Figure 6-3. The PtSalesPerson class<br \/>\n\u56fe 6-3\u3002 PtSalesPerson \u7c7b<\/p>\n<p>PtSalesPerson is a class representing, of course, a part-time salesperson. For the sake of argument, let\u2019s say you want to ensure that no other developer is able to subclass from PTSalesPerson. To prevent others from extending a class, use the sealed keyword.<br \/>\nPtSalesperson\u662f\u4e00\u4e2a\u4ee3\u8868\u517c\u804c\u9500\u552e\u4eba\u5458\u7684\u73ed\u7ea7\u3002\u4e3a\u4e86\u4fbf\u4e8e\u8ba8\u8bba\uff0c\u5047\u8bbe\u60a8\u60f3\u786e\u4fdd\u6ca1\u6709\u5176\u4ed6\u5f00\u53d1\u4eba\u5458\u80fd\u591f\u4ece PTSalesPerson \u8fdb\u884c\u5b50\u7c7b\u5316\u3002\u82e5\u8981\u9632\u6b62\u5176\u4ed6\u4eba\u6269\u5c55\u7c7b\uff0c\u8bf7\u4f7f\u7528\u5bc6\u5c01\u5173\u952e\u5b57\u3002<\/p>\n<p>namespace Employees;<br \/>\nsealed class PtSalesPerson : SalesPerson<br \/>\n{<br \/>\npublic PtSalesPerson(string fullName, int age, int empId, float currPay, string ssn, int numbOfSales)<br \/>\n: base(fullName, age, empId, currPay, ssn, numbOfSales)<br \/>\n{<br \/>\n}<br \/>\n\/\/ Assume other members here...<br \/>\n}<\/p>\n<h2>Understanding Inheritance with Record Types (New 9.0)<\/h2>\n<p>\u4e86\u89e3\u8bb0\u5f55\u7c7b\u578b\u7684\u7ee7\u627f\uff08\u65b0 9.0\uff09<\/p>\n<p>The new C# 9.0 record types also support inheritance. To explore this, place your work in the Employees project on hold and create a new console app named RecordInheritance.<br \/>\n\u65b0\u7684 C# 9.0 \u8bb0\u5f55\u7c7b\u578b\u8fd8\u652f\u6301\u7ee7\u627f\u3002\u82e5\u8981\u63a2\u7d22\u8fd9\u4e00\u70b9\uff0c\u8bf7\u5c06\u201c\u5458\u5de5\u201d\u9879\u76ee\u4e2d\u7684\u5de5\u4f5c\u7f6e\u4e8e\u6682\u505c\u72b6\u6001\uff0c\u5e76\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a\u201c\u8bb0\u5f55\u7ee7\u627f\u201d\u7684\u65b0\u63a7\u5236\u53f0\u5e94\u7528\u3002<\/p>\n<h2>Inheritance for Record Types with Standard Properties<\/h2>\n<p>\u5177\u6709\u6807\u51c6\u5c5e\u6027\u7684\u8bb0\u5f55\u7c7b\u578b\u7684\u7ee7\u627f<\/p>\n<p>Add two new files named Car.cs and MiniVan.cs, and add the following record defining code into their respective files:<br \/>\n\u6dfb\u52a0\u4e24\u4e2a\u540d\u4e3a Car.cs \u548c MiniVan.cs \u7684\u65b0\u6587\u4ef6\uff0c\u5e76\u5c06\u4ee5\u4e0b\u8bb0\u5f55\u5b9a\u4e49\u4ee3\u7801\u6dfb\u52a0\u5230\u5404\u81ea\u7684\u6587\u4ef6\u4e2d\uff1a<\/p>\n<p>\/\/Car.cs<br \/>\nnamespace RecordInheritance;<br \/>\n\/\/Car record type public record Car<br \/>\n{<br \/>\npublic string Make { get; init; } public string Model { get; init; } public string Color { get; init; }<\/p>\n<p>public Car(string make, string model, string color)<br \/>\n{<br \/>\nMake = make;<br \/>\nModel = model;<br \/>\nColor = color;<br \/>\n}<br \/>\n}<\/p>\n<p>\/\/MiniVan.cs<br \/>\nnamespace RecordInheritance;<br \/>\n\/\/MiniVan record type<br \/>\npublic sealed record MiniVan : Car<br \/>\n{<br \/>\npublic int Seating { get; init; }<br \/>\npublic MiniVan(string make, string model, string color, int seating) : base(make, model, color)<br \/>\n{<br \/>\nSeating = seating;<br \/>\n}<br \/>\n}<\/p>\n<p>Notice that there isn\u2019t much difference between these examples using record types and the previous examples using classes. The sealed access modifier on the record type prevents other record types from deriving from the sealed record types. Although not used in the listed examples, the protected access modifier on properties and methods behave the same as with class inheritance. You will also find the remaining topics in this chapter work with inherited record types. This is because record types are just a special type of class (as detailed in Chapter 5).<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u8fd9\u4e9b\u4f7f\u7528\u8bb0\u5f55\u7c7b\u578b\u7684\u793a\u4f8b\u4e0e\u524d\u9762\u4f7f\u7528\u7c7b\u7684\u793a\u4f8b\u4e4b\u95f4\u6ca1\u6709\u592a\u5927\u533a\u522b\u3002\u8bb0\u5f55\u7c7b\u578b\u4e0a\u7684\u5bc6\u5c01\u8bbf\u95ee\u4fee\u9970\u7b26\u53ef\u9632\u6b62\u4ece\u5bc6\u5c01\u8bb0\u5f55\u7c7b\u578b\u6d3e\u751f\u5176\u4ed6\u8bb0\u5f55\u7c7b\u578b\u3002\u5c3d\u7ba1\u5728\u5217\u51fa\u7684\u793a\u4f8b\u4e2d\u672a\u4f7f\u7528\uff0c\u4f46\u5c5e\u6027\u548c\u65b9\u6cd5\u4e0a\u7684\u53d7\u4fdd\u62a4\u8bbf\u95ee\u4fee\u9970\u7b26\u7684\u884c\u4e3a\u4e0e\u7c7b\u7ee7\u627f\u76f8\u540c\u3002\u60a8\u8fd8\u5c06\u53d1\u73b0\u672c\u7ae0\u4e2d\u7684\u5176\u4f59\u4e3b\u9898\u4f7f\u7528\u7ee7\u627f\u7684\u8bb0\u5f55\u7c7b\u578b\u3002\u8fd9\u662f\u56e0\u4e3a\u8bb0\u5f55\u7c7b\u578b\u53ea\u662f\u4e00\u79cd\u7279\u6b8a\u7c7b\u578b\u7684\u7c7b\uff08\u8be6\u89c1\u7b2c5\u7ae0\uff09\u3002<\/p>\n<p>Record types also include implicit casts to their base class, as shown in the following code:<br \/>\n\u8bb0\u5f55\u7c7b\u578b\u8fd8\u5305\u62ec\u5bf9\u5176\u57fa\u7c7b\u7684\u9690\u5f0f\u5f3a\u5236\u8f6c\u6362\uff0c\u5982\u4ee5\u4e0b\u4ee3\u7801\u6240\u793a\uff1a<\/p>\n<p>using RecordInheritance; Console.WriteLine(&quot;Record type inheritance!&quot;); Car c = new Car(&quot;Honda&quot;,&quot;Pilot&quot;,&quot;Blue&quot;);<br \/>\nMiniVan m = new MiniVan(&quot;Honda&quot;, &quot;Pilot&quot;, &quot;Blue&quot;,10); Console.WriteLine($&quot;Checking MiniVan is-a Car:{m is Car}&quot;);<\/p>\n<p>As one would expect, the output from the check m is that Car returns true, as the following output shows:<br \/>\n\u6b63\u5982\u4eba\u4eec\u6240\u671f\u671b\u7684\u90a3\u6837\uff0c\u68c0\u67e5 m \u7684\u8f93\u51fa\u662f Car \u8fd4\u56de true\uff0c\u5982\u4ee5\u4e0b\u8f93\u51fa\u6240\u793a\uff1a<\/p>\n<p>Record type inheritance! Checking minvan is-a car:True<\/p>\n<p>It's important to note that even though record types are specialized classes, you cannot cross-inherit between classes and records. To be clear, classes cannot inherit from record types, and record types cannot inherit from classes. Consider the following code, and notice that the last two examples won\u2019t compile:<br \/>\n\u8bf7\u52a1\u5fc5\u6ce8\u610f\uff0c\u5373\u4f7f\u8bb0\u5f55\u7c7b\u578b\u662f\u4e13\u7528\u7c7b\uff0c\u4e5f\u4e0d\u80fd\u5728\u7c7b\u548c\u8bb0\u5f55\u4e4b\u95f4\u4ea4\u53c9\u7ee7\u627f\u3002\u9700\u8981\u660e\u786e\u7684\u662f\uff0c\u7c7b\u4e0d\u80fd\u4ece\u8bb0\u5f55\u7c7b\u578b\u7ee7\u627f\uff0c\u8bb0\u5f55\u7c7b\u578b\u4e0d\u80fd\u4ece\u7c7b\u7ee7\u627f\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b\u4ee3\u7801\uff0c\u8bf7\u6ce8\u610f\u6700\u540e\u4e24\u4e2a\u793a\u4f8b\u65e0\u6cd5\u7f16\u8bd1\uff1a<\/p>\n<p>namespace RecordInheritance; public class TestClass { } public record TestRecord { }<\/p>\n<p>\/\/Classes cannot inherit records<br \/>\n\/\/ public class Test2 : TestRecord { }<\/p>\n<p>\/\/Records types cannot inherit from classes<br \/>\n\/\/ public record Test2 : TestClass { }<\/p>\n<h2>Inheritance for Record Types with Positional Parameters<\/h2>\n<p>\u5177\u6709\u4f4d\u7f6e\u53c2\u6570\u7684\u8bb0\u5f55\u7c7b\u578b\u7684\u7ee7\u627f<\/p>\n<p>Inheritance also works with positional record types. The derived record declares positional parameters for all of the parameters in the base record. The derived record doesn\u2019t hide them but uses them from the base record. The derived record only creates and initializes properties that are not on the base record.<br \/>\n\u7ee7\u627f\u4e5f\u9002\u7528\u4e8e\u4f4d\u7f6e\u8bb0\u5f55\u7c7b\u578b\u3002\u6d3e\u751f\u8bb0\u5f55\u4e3a\u57fa\u672c\u8bb0\u5f55\u4e2d\u7684\u6240\u6709\u53c2\u6570\u58f0\u660e\u4f4d\u7f6e\u53c2\u6570\u3002\u6d3e\u751f\u8bb0\u5f55\u4e0d\u4f1a\u9690\u85cf\u5b83\u4eec\uff0c\u800c\u662f\u4ece\u57fa\u672c\u8bb0\u5f55\u4e2d\u4f7f\u7528\u5b83\u4eec\u3002\u6d3e\u751f\u8bb0\u5f55\u4ec5\u521b\u5efa\u548c\u521d\u59cb\u5316\u4e0d\u5728\u57fa\u8bb0\u5f55\u4e0a\u7684\u5c5e\u6027\u3002<\/p>\n<p>To see this in action, create a new file named PositionalRecordTypes.cs in your project. Add the following code into your file:<br \/>\n\u82e5\u8981\u67e5\u770b\u6b64\u64cd\u4f5c\u7684\u5b9e\u9645\u6548\u679c\uff0c\u8bf7\u5728\u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a PositionalRecordTypes.cs \u7684\u65b0\u6587\u4ef6\u3002\u5c06\u4ee5\u4e0b\u4ee3\u7801\u6dfb\u52a0\u5230\u6587\u4ef6\u4e2d\uff1a<\/p>\n<p>namespace RecordInheritance;<br \/>\npublic record PositionalCar (string Make, string Model, string Color);<br \/>\npublic record PositionalMiniVan (string Make, string Model, string Color, int seating)<br \/>\n: PositionalCar(Make, Model, Color);<\/p>\n<p>public record MotorCycle(string Make, string Model);<br \/>\npublic record Scooter(string Make, string Model) : MotorCycle(Make,Model); public record FancyScooter(string Make, string Model, string FancyColor)<br \/>\n: Scooter(Make, Model);<\/p>\n<p>Add the following code to show what you already know to be true: that the positional record types work exactly the same as record types:<br \/>\n\u6dfb\u52a0\u4ee5\u4e0b\u4ee3\u7801\u4ee5\u663e\u793a\u60a8\u5df2\u7ecf\u77e5\u9053\u7684\u60c5\u51b5\uff1a\u4f4d\u7f6e\u8bb0\u5f55\u7c7b\u578b\u7684\u5de5\u4f5c\u65b9\u5f0f\u4e0e\u8bb0\u5f55\u7c7b\u578b\u5b8c\u5168\u76f8\u540c\uff1a<\/p>\n<p>PositionalCar pc = new PositionalCar(&quot;Honda&quot;, &quot;Pilot&quot;, &quot;Blue&quot;); PositionalMiniVan pm = new PositionalMiniVan(&quot;Honda&quot;, &quot;Pilot&quot;, &quot;Blue&quot;, 10);<br \/>\nConsole.WriteLine($&quot;Checking PositionalMiniVan is-a PositionalCar:{pm is PositionalCar}&quot;);<\/p>\n<h2>Nondestructive Mutation with Inherited Record Types<\/h2>\n<p>\u5177\u6709\u7ee7\u627f\u8bb0\u5f55\u7c7b\u578b\u7684\u975e\u7834\u574f\u6027\u7a81\u53d8<\/p>\n<p>When creating new record type instances using the with expression, the resulting record type is the same runtime type of the operand. Take the following example:<br \/>\n\u4f7f\u7528 with \u8868\u8fbe\u5f0f\u521b\u5efa\u65b0\u7684\u8bb0\u5f55\u7c7b\u578b\u5b9e\u4f8b\u65f6\uff0c\u751f\u6210\u7684\u8bb0\u5f55\u7c7b\u578b\u4e0e\u64cd\u4f5c\u6570\u7684\u8fd0\u884c\u65f6\u7c7b\u578b\u76f8\u540c\u3002\u4e3e\u4e2a\u4f8b\u5b50\uff1a<\/p>\n<p>MotorCycle mc = new FancyScooter(&quot;Harley&quot;, &quot;Lowrider&quot;,&quot;Gold&quot;); Console.WriteLine($&quot;mc is a FancyScooter: {mc is FancyScooter}&quot;); MotorCycle mc2 = mc with { Make = &quot;Harley&quot;, Model = &quot;Lowrider&quot; }; Console.WriteLine($&quot;mc2 is a FancyScooter: {mc2 is FancyScooter}&quot;);<\/p>\n<p>In both of these examples, the runtime type of the instances is FancyScooter, not MotorCycle:<br \/>\n\u5728\u8fd9\u4e24\u4e2a\u793a\u4f8b\u4e2d\uff0c\u5b9e\u4f8b\u7684\u8fd0\u884c\u65f6\u7c7b\u578b\u662f FancyScooter\uff0c\u800c\u4e0d\u662f MotorCycle\uff1a<\/p>\n<p>Record type inheritance! mc is a FancyScooter: True<br \/>\nmc2 is a FancyScooter: True<\/p>\n<h2>Equality with Inherited Record Types<\/h2>\n<p>\u5728\u8fd9\u4e24\u4e2a\u793a\u4f8b\u4e2d\uff0c\u5b9e\u4f8b\u7684\u8fd0\u884c\u65f6\u7c7b\u578b\u662f FancyScooter\uff0c\u800c\u4e0d\u662f MotorCycle\uff1a<\/p>\n<p>Recall from Chapter 5 that record types use value semantics to determine equality. One additional detail regarding record types is that the type of the record is part of the equality consideration. Take into consideration the MotorCycle and Scooter types from earlier:<br \/>\n\u56de\u60f3\u4e00\u4e0b\u7b2c 5 \u7ae0\uff0c\u8bb0\u5f55\u7c7b\u578b\u4f7f\u7528\u503c\u8bed\u4e49\u6765\u786e\u5b9a\u76f8\u7b49\u6027\u3002\u6709\u5173\u8bb0\u5f55\u7c7b\u578b\u7684\u53e6\u4e00\u4e2a\u8be6\u7ec6\u4fe1\u606f\u662f\uff0c\u8bb0\u5f55\u7684\u7c7b\u578b\u662f\u76f8\u7b49\u6027\u8003\u8651\u7684\u4e00\u90e8\u5206\u3002\u8003\u8651\u524d\u9762\u7684\u6469\u6258\u8f66\u548c\u8e0f\u677f\u8f66\u7c7b\u578b\uff1a<\/p>\n<p>public record MotorCycle(string Make, string Model);<br \/>\npublic record Scooter(string Make, string Model) : MotorCycle(Make,Model);<\/p>\n<p>Ignoring the fact that typically inherited classes extend base classes, these simple examples define two different record types that have the same properties. When creating instances with the same values for the properties, they fail the equality test due to being different types. Take the following code and results, for example:<br \/>\n\u5ffd\u7565\u901a\u5e38\u7ee7\u627f\u7684\u7c7b\u6269\u5c55\u57fa\u7c7b\u7684\u4e8b\u5b9e\uff0c\u8fd9\u4e9b\u7b80\u5355\u793a\u4f8b\u5b9a\u4e49\u4e86\u5177\u6709\u76f8\u540c\u5c5e\u6027\u7684\u4e24\u79cd\u4e0d\u540c\u8bb0\u5f55\u7c7b\u578b\u3002\u4e3a\u5c5e\u6027\u521b\u5efa\u5177\u6709\u76f8\u540c\u503c\u7684\u5b9e\u4f8b\u65f6\uff0c\u7531\u4e8e\u7c7b\u578b\u4e0d\u540c\uff0c\u5b83\u4eec\u65e0\u6cd5\u901a\u8fc7\u76f8\u7b49\u6027\u6d4b\u8bd5\u3002\u4ee5\u4ee5\u4e0b\u4ee3\u7801\u548c\u7ed3\u679c\u4e3a\u4f8b\uff1a<\/p>\n<p>MotorCycle mc3 = new MotorCycle(&quot;Harley&quot;,&quot;Lowrider&quot;); Scooter sc = new Scooter(&quot;Harley&quot;, &quot;Lowrider&quot;);<br \/>\nConsole.WriteLine($&quot;MotorCycle and Scooter are equal: {Equals(mc3,sc)}&quot;);<\/p>\n<p>Record type inheritance!<br \/>\nMotorCycle and Scooter are equal: False<\/p>\n<p>The reason for the two not being equal is that the equality check with record types uses the runtime type, not the declared type. The following example further illustrates this:<br \/>\n\u4e24\u8005\u4e0d\u76f8\u7b49\u7684\u539f\u56e0\u662f\u8bb0\u5f55\u7c7b\u578b\u7684\u76f8\u7b49\u6027\u68c0\u67e5\u4f7f\u7528\u8fd0\u884c\u65f6\u7c7b\u578b\uff0c\u800c\u4e0d\u662f\u58f0\u660e\u7684\u7c7b\u578b\u3002\u4ee5\u4e0b\u793a\u4f8b\u8fdb\u4e00\u6b65\u8bf4\u660e\u4e86\u8fd9\u4e00\u70b9\uff1a<\/p>\n<p>MotorCycle mc3 = new MotorCycle(&quot;Harley&quot;,&quot;Lowrider&quot;); MotorCycle scMotorCycle = new Scooter(&quot;Harley&quot;, &quot;Lowrider&quot;);<br \/>\nConsole.WriteLine($&quot;MotorCycle and Scooter Motorcycle are equal: {Equals(mc3,scMotorCycle)}&quot;);<\/p>\n<p>Notice that both the mc3 and scMotorCycle variables are declared as MotorCycle record types. Despite this, the types are not equal, since the runtime types are different:<br \/>\n\u8bf7\u6ce8\u610f\uff0cmc3 \u548c scMotorCycle \u53d8\u91cf\u90fd\u58f0\u660e\u4e3a MotorCycle \u8bb0\u5f55\u7c7b\u578b\u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u7c7b\u578b\u5e76\u4e0d\u76f8\u7b49\uff0c\u56e0\u4e3a\u8fd0\u884c\u65f6\u7c7b\u578b\u4e0d\u540c\uff1a<\/p>\n<p>Record type inheritance!<br \/>\nMotorCycle and Scooter Motorcycle are equal: False<\/p>\n<h2>Deconstructor Behavior with Inherited Record Types<\/h2>\n<p>\u5177\u6709\u7ee7\u627f\u8bb0\u5f55\u7c7b\u578b\u7684\u89e3\u6784\u51fd\u6570\u884c\u4e3a<\/p>\n<p>The Deconstruct() method of a derived record returns the values of all positional properties of the declared, compile-time type. In this first example, the FancyColor property is not deconstructed because the compile- time type is MotorCycle:<br \/>\n\u6d3e\u751f\u8bb0\u5f55\u7684 Deconstruct\uff08\uff09 \u65b9\u6cd5\u8fd4\u56de\u58f0\u660e\u7684\u7f16\u8bd1\u65f6\u7c7b\u578b\u7684\u6240\u6709\u4f4d\u7f6e\u5c5e\u6027\u7684\u503c\u3002\u5728\u7b2c\u4e00\u4e2a\u793a\u4f8b\u4e2d\uff0c\u672a\u89e3\u6784 FancyColor \u5c5e\u6027\uff0c\u56e0\u4e3a\u7f16\u8bd1\u65f6\u7c7b\u578b\u4e3a MotorCycle\uff1a<\/p>\n<p>MotorCycle mc = new FancyScooter(&quot;Harley&quot;, &quot;Lowrider&quot;,&quot;Gold&quot;); var (make1, model1) = mc; \/\/doesn't deconstruct FancyColor<br \/>\nvar (make2, model2, fancyColor2) = (FancyScooter)mc;<\/p>\n<p>However, if the variable is cast to the derived type, then all of the positional properties of the derived type are deconstructed, as shown here:<br \/>\n\u4f46\u662f\uff0c\u5982\u679c\u5c06\u53d8\u91cf\u5f3a\u5236\u8f6c\u6362\u4e3a\u6d3e\u751f\u7c7b\u578b\uff0c\u5219\u4f1a\u89e3\u6784\u6d3e\u751f\u7c7b\u578b\u7684\u6240\u6709\u4f4d\u7f6e\u5c5e\u6027\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>MotorCycle mc = new FancyScooter(&quot;Harley&quot;, &quot;Lowrider&quot;,&quot;Gold&quot;); var (make2, model2, fancyColor2) = (FancyScooter)mc;<\/p>\n<h2>Programming for Containment\/Delegation<\/h2>\n<p>\u904f\u5236\/\u59d4\u6d3e\u7f16\u7a0b<\/p>\n<p>Recall that code reuse comes in two flavors. You have just explored the classical \u201cis-a\u201d relationship. Before you examine the third pillar of OOP (polymorphism), let\u2019s examine the \u201chas-a\u201d relationship (also known as the containment\/delegation model or aggregation). Returning to the Employees project, create a new file named BenefitPackage.cs and add the code to model an employee benefits package, as follows:<br \/>\n\u56de\u60f3\u4e00\u4e0b\uff0c\u4ee3\u7801\u91cd\u7528\u6709\u4e24\u79cd\u5f62\u5f0f\u3002\u60a8\u521a\u521a\u63a2\u7d22\u4e86\u7ecf\u5178\u7684\u201cis-a\u201d\u5173\u7cfb\u3002\u5728\u68c0\u67e5 OOP\uff08\u591a\u6001\u6027\uff09\u7684\u7b2c\u4e09\u4e2a\u652f\u67f1\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u68c0\u67e5\u4e00\u4e0b\u201chas-a\u201d\u5173\u7cfb\uff08\u4e5f\u79f0\u4e3a\u5305\u542b\/\u59d4\u6d3e\u6a21\u578b\u6216\u805a\u5408\uff09\u3002\u8fd4\u56de\u5230\u201c\u5458\u5de5\u201d\u9879\u76ee\uff0c\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a BenefitPack \u7684\u65b0\u6587\u4ef6.cs\u5e76\u6dfb\u52a0\u4ee3\u7801\u4ee5\u5bf9\u5458\u5de5\u798f\u5229\u5305\u8fdb\u884c\u5efa\u6a21\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>namespace Employees;<br \/>\n\/\/ This new type will function as a contained class. class BenefitPackage<br \/>\n{<br \/>\n\/\/ Assume we have other members that represent<br \/>\n\/\/ dental\/health benefits, and so on. public double ComputePayDeduction()<br \/>\n{<br \/>\nreturn 125.0;<br \/>\n}<br \/>\n}<\/p>\n<p>Obviously, it would be rather odd to establish an \u201cis-a\u201d relationship between the BenefitPackage class and the employee types. (Employee \u201cis-a\u201d BenefitPackage? I don\u2019t think so.) However, it should be clear that some sort of relationship between the two could be established. In short, you would like to express the idea that each employee \u201chas-a\u201d BenefitPackage. To do so, you can update the Employee class definition as follows:<br \/>\n\u663e\u7136\uff0c\u5728\u798f\u5229\u5305\u7c7b\u522b\u548c\u5458\u5de5\u7c7b\u578b\u4e4b\u95f4\u5efa\u7acb\u201c\u662f\u201d\u5173\u7cfb\u662f\u76f8\u5f53\u5947\u602a\u7684\u3002\uff08\u5458\u5de5\u201c\u662f\u201d\u798f\u5229\u5305\uff1f\u6211\u4e0d\u8fd9\u4e48\u8ba4\u4e3a\u3002\u4f46\u662f\uff0c\u5e94\u8be5\u660e\u786e\u7684\u662f\uff0c\u4e24\u8005\u4e4b\u95f4\u53ef\u4ee5\u5efa\u7acb\u67d0\u79cd\u5173\u7cfb\u3002\u7b80\u800c\u8a00\u4e4b\uff0c\u60a8\u60f3\u8868\u8fbe\u6bcf\u4e2a\u5458\u5de5\u201c\u90fd\u6709\u201d\u798f\u5229\u5305\u7684\u60f3\u6cd5\u3002\u4e3a\u6b64\uff0c\u60a8\u53ef\u4ee5\u66f4\u65b0 Employee \u7c7b\u5b9a\u4e49\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>\/\/ Employees now have benefits. partial class Employee<br \/>\n{<br \/>\n\/\/ Contain a BenefitPackage object.<br \/>\nprotected BenefitPackage EmpBenefits = new BenefitPackage();<br \/>\n...<br \/>\n}<\/p>\n<p>At this point, you have successfully contained another object. However, exposing the functionality of the contained object to the outside world requires delegation. Delegation is simply the act of adding public members to the containing class that use the contained object\u2019s functionality.<br \/>\n\u6b64\u65f6\uff0c\u60a8\u5df2\u6210\u529f\u5305\u542b\u53e6\u4e00\u4e2a\u5bf9\u8c61\u3002\u4f46\u662f\uff0c\u5411\u5916\u90e8\u4e16\u754c\u516c\u5f00\u6240\u5305\u542b\u5bf9\u8c61\u7684\u529f\u80fd\u9700\u8981\u59d4\u6d3e\u3002\u59d4\u6d3e\u53ea\u662f\u5c06\u516c\u5171\u6210\u5458\u6dfb\u52a0\u5230\u4f7f\u7528\u6240\u5305\u542b\u5bf9\u8c61\u7684\u529f\u80fd\u7684\u5305\u542b\u7c7b\u7684\u64cd\u4f5c\u3002<\/p>\n<p>For example, you could update the Employee class to expose the contained empBenefits object using a custom property, as well as make use of its functionality internally using a new method named GetBenefitCost().<br \/>\n\u4f8b\u5982\uff0c\u60a8\u53ef\u4ee5\u66f4\u65b0 Employee \u7c7b\u4ee5\u4f7f\u7528\u81ea\u5b9a\u4e49\u5c5e\u6027\u516c\u5f00\u5305\u542b\u7684 empBenefits \u5bf9\u8c61\uff0c\u4ee5\u53ca\u4f7f\u7528\u540d\u4e3a GetBenefitCost\uff08\uff09 \u7684\u65b0\u65b9\u6cd5\u5728\u5185\u90e8\u4f7f\u7528\u5176\u529f\u80fd\u3002<\/p>\n<p>partial class Employee<br \/>\n{<br \/>\n\/\/ Contain a BenefitPackage object.<br \/>\nprotected BenefitPackage EmpBenefits = new BenefitPackage();<\/p>\n<p>\/\/ Expose certain benefit behaviors of object. public double GetBenefitCost()<br \/>\n=&gt; EmpBenefits.ComputePayDeduction();<\/p>\n<p>\/\/ Expose object through a custom property. public BenefitPackage Benefits<br \/>\n{<br \/>\nget { return EmpBenefits; } set { EmpBenefits = value; }<br \/>\n}<br \/>\n}<\/p>\n<p>In the following updated code, notice how you can interact with the internal BenefitsPackage type defined by the Employee type:<br \/>\n\u5728\u4ee5\u4e0b\u66f4\u65b0\u7684\u4ee3\u7801\u4e2d\uff0c\u8bf7\u6ce8\u610f\u5982\u4f55\u4e0e\u5458\u5de5\u7c7b\u578b\u5b9a\u4e49\u7684\u5185\u90e8\u798f\u5229\u5305\u7c7b\u578b\u8fdb\u884c\u4ea4\u4e92\uff1a<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> The Employee Class Hierarchy <\/em><\/strong><\/strong>\\n&quot;);<br \/>\n...<br \/>\nManager chucky = new Manager(&quot;Chucky&quot;, 50, 92, 100000, &quot;333-23-2322&quot;, 9000); double cost = chucky.GetBenefitCost();<br \/>\nConsole.WriteLine($&quot;Benefit Cost: {cost}&quot;); Console.ReadLine();<\/p>\n<h2>Understanding Nested Type Definitions<\/h2>\n<p>\u4e86\u89e3\u5d4c\u5957\u7c7b\u578b\u5b9a\u4e49<\/p>\n<p>Chapter 5 briefly mentioned the concept of nested types, which is a spin on the \u201chas-a\u201d relationship you have just examined. In C# (as well as other .NET languages), it is possible to define a type (enum, class, interface, struct, or delegate) directly within the scope of a class or structure. When you have done so, the nested (or \u201cinner\u201d) type is considered a member of the nesting (or \u201couter\u201d) class and in the eyes of the runtime can be manipulated like any other member (fields, properties, methods, and events). The syntax used to nest a type is quite straightforward.<br \/>\n\u7b2c5\u7ae0\u7b80\u8981\u63d0\u5230\u4e86\u5d4c\u5957\u7c7b\u578b\u7684\u6982\u5ff5\uff0c\u8fd9\u662f\u5bf9\u4f60\u521a\u521a\u7814\u7a76\u7684\u201chas-a\u201d\u5173\u7cfb\u7684\u65cb\u8f6c\u3002\u5728 C#\uff08\u4ee5\u53ca\u5176\u4ed6 .NET \u8bed\u8a00\uff09\u4e2d\uff0c\u53ef\u4ee5\u76f4\u63a5\u5728\u7c7b\u6216\u7ed3\u6784\u7684\u8303\u56f4\u5185\u5b9a\u4e49\u7c7b\u578b\uff08\u679a\u4e3e\u3001\u7c7b\u3001\u63a5\u53e3\u3001\u7ed3\u6784\u6216\u59d4\u6258\uff09\u3002\u6267\u884c\u6b64\u64cd\u4f5c\u540e\uff0c\u5d4c\u5957\uff08\u6216\u201c\u5185\u90e8\u201d\uff09\u7c7b\u578b\u88ab\u89c6\u4e3a\u5d4c\u5957\uff08\u6216\u201c\u5916\u90e8\u201d\uff09\u7c7b\u7684\u6210\u5458\uff0c\u5e76\u4e14\u5728\u8fd0\u884c\u65f6\u773c\u4e2d\u53ef\u4ee5\u50cf\u4efb\u4f55\u5176\u4ed6\u6210\u5458\uff08\u5b57\u6bb5\u3001\u5c5e\u6027\u3001\u65b9\u6cd5\u548c\u4e8b\u4ef6\uff09\u4e00\u6837\u8fdb\u884c\u64cd\u4f5c\u3002\u7528\u4e8e\u5d4c\u5957\u7c7b\u578b\u7684\u8bed\u6cd5\u975e\u5e38\u7b80\u5355\u3002<\/p>\n<p>public class OuterClass<br \/>\n{<br \/>\n\/\/ A public nested type can be used by anybody. public class PublicInnerClass {}<\/p>\n<p>\/\/ A private nested type can only be used by members<br \/>\n\/\/ of the containing class. private class PrivateInnerClass {}<br \/>\n}<\/p>\n<p>Although the syntax is fairly clear, understanding why you would want to do this might not be readily apparent. To understand this technique, ponder the following traits of nesting a type:<br \/>\n\u5c3d\u7ba1\u8bed\u6cd5\u76f8\u5f53\u6e05\u6670\uff0c\u4f46\u7406\u89e3\u4e3a\u4ec0\u4e48\u8981\u8fd9\u6837\u505a\u53ef\u80fd\u5e76\u4e0d\u660e\u663e\u3002\u82e5\u8981\u7406\u89e3\u6b64\u6280\u672f\uff0c\u8bf7\u601d\u8003\u5d4c\u5957\u7c7b\u578b\u7684\u4ee5\u4e0b\u7279\u5f81\uff1a<\/p>\n<p>\u2022   Nested types allow you to gain complete control over the access level of the inner type because they may be declared privately (recall that non-nested classes cannot be declared using the private keyword).<br \/>\n\u5d4c\u5957\u7c7b\u578b\u5141\u8bb8\u60a8\u5b8c\u5168\u63a7\u5236\u5185\u90e8\u7c7b\u578b\u7684\u8bbf\u95ee\u7ea7\u522b\uff0c\u56e0\u4e3a\u5b83\u4eec\u53ef\u4ee5\u79c1\u4e0b\u58f0\u660e\uff08\u56de\u60f3\u4e00\u4e0b\uff0c\u975e\u5d4c\u5957\u7c7b\u4e0d\u80fd\u4f7f\u7528 private \u5173\u952e\u5b57\u58f0\u660e\uff09\u3002<\/p>\n<p>\u2022   Because a nested type is a member of the containing class, it can access private members of the containing class.<br \/>\n\u7531\u4e8e\u5d4c\u5957\u7c7b\u578b\u662f\u5305\u542b\u7c7b\u7684\u6210\u5458\uff0c\u56e0\u6b64\u5b83\u53ef\u4ee5\u8bbf\u95ee\u5305\u542b\u7c7b\u7684\u79c1\u6709\u6210\u5458\u3002<br \/>\n\u2022   Often, a nested type is useful only as a helper for the outer class and is not intended for use by the outside world.<br \/>\n\u901a\u5e38\uff0c\u5d4c\u5957\u7c7b\u578b\u4ec5\u7528\u4f5c\u5916\u90e8\u7c7b\u7684\u5e2e\u52a9\u7a0b\u5e8f\uff0c\u4e0d\u9002\u5408\u5916\u90e8\u4e16\u754c\u4f7f\u7528\u3002<\/p>\n<p>When a type nests another class type, it can create member variables of the type, just as it would for any point of data. However, if you want to use a nested type from outside the containing type, you must qualify it by the scope of the nesting type. Consider the following code:<br \/>\n\u5f53\u4e00\u4e2a\u7c7b\u578b\u5d4c\u5957\u53e6\u4e00\u4e2a\u7c7b\u7c7b\u578b\u65f6\uff0c\u5b83\u53ef\u4ee5\u521b\u5efa\u8be5\u7c7b\u578b\u7684\u6210\u5458\u53d8\u91cf\uff0c\u5c31\u50cf\u5b83\u5bf9\u4efb\u4f55\u6570\u636e\u70b9\u6240\u505a\u7684\u90a3\u6837\u3002\u4f46\u662f\uff0c\u5982\u679c\u8981\u4f7f\u7528\u5305\u542b\u7c7b\u578b\u5916\u90e8\u7684\u5d4c\u5957\u7c7b\u578b\uff0c\u5219\u5fc5\u987b\u901a\u8fc7\u5d4c\u5957\u7c7b\u578b\u7684\u4f5c\u7528\u57df\u5bf9\u5176\u8fdb\u884c\u9650\u5b9a\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<p>\/\/ Create and use the public inner class. OK! OuterClass.PublicInnerClass inner;<br \/>\ninner = new OuterClass.PublicInnerClass();<\/p>\n<p>\/\/ Compiler Error! Cannot access the private class. OuterClass.PrivateInnerClass inner2;<br \/>\ninner2 = new OuterClass.PrivateInnerClass();<\/p>\n<p>To use this concept within the employee\u2019s example, assume you have now nested the BenefitPackage directly within the Employee class type.<br \/>\n\u8981\u5728\u5458\u5de5\u793a\u4f8b\u4e2d\u4f7f\u7528\u6b64\u6982\u5ff5\uff0c\u5047\u8bbe\u60a8\u73b0\u5728\u5df2\u7ecf\u5d4c\u5957\u4e86\u798f\u5229\u5305\u76f4\u63a5\u5728\u5458\u5de5\u7c7b\u7c7b\u578b\u4e2d\u3002<\/p>\n<p>partial class Employee<br \/>\n{<br \/>\npublic class BenefitPackage<br \/>\n{<br \/>\n\/\/ Assume we have other members that represent<br \/>\n\/\/ dental\/health benefits, and so on. public double ComputePayDeduction()<br \/>\n{<br \/>\nreturn 125.0;<br \/>\n}<br \/>\n}<br \/>\n...<br \/>\n}<\/p>\n<p>The nesting process can be as \u201cdeep\u201d as you require. For example, assume you want to create an enumeration named BenefitPackageLevel, which documents the various benefit levels an employee may choose. To programmatically enforce the tight connection between Employee, BenefitPackage, and BenefitPackageLevel, you could nest the enumeration as follows:<br \/>\n\u5d4c\u5957\u8fc7\u7a0b\u53ef\u4ee5\u6839\u636e\u9700\u8981\u201c\u6df1\u5ea6\u201d\u3002\u4f8b\u5982\uff0c\u5047\u8bbe\u60a8\u8981\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a BenefitPackageLevel \u7684\u679a\u4e3e\uff0c\u8be5\u679a\u4e3e\u8bb0\u5f55\u4e86\u5458\u5de5\u53ef\u4ee5\u9009\u62e9\u7684\u5404\u79cd\u798f\u5229\u7ea7\u522b\u3002\u82e5\u8981\u4ee5\u7f16\u7a0b\u65b9\u5f0f\u5f3a\u5236\u5b9e\u65bd Employee\u3001BenefitPackage \u548c BenefitPackageLevel \u4e4b\u95f4\u7684\u7d27\u5bc6\u8fde\u63a5\uff0c\u53ef\u4ee5\u6309\u5982\u4e0b\u6240\u793a\u5d4c\u5957\u679a\u4e3e\uff1a<\/p>\n<p>\/\/ Employee nests BenefitPackage. public partial class Employee<br \/>\n{<br \/>\n\/\/ BenefitPackage nests BenefitPackageLevel. public class BenefitPackage<br \/>\n{<br \/>\npublic enum BenefitPackageLevel<br \/>\n{<br \/>\nStandard, Gold, Platinum<br \/>\n}<\/p>\n<p>public double ComputePayDeduction()<br \/>\n{<br \/>\nreturn 125.0;<br \/>\n}<br \/>\n}<br \/>\n...<br \/>\n}<\/p>\n<p>Because of the nesting relationships, note how you are required to make use of this enumeration:<br \/>\n\u7531\u4e8e\u5d4c\u5957\u5173\u7cfb\uff0c\u8bf7\u6ce8\u610f\u5982\u4f55\u8981\u6c42\u4f7f\u7528\u6b64\u679a\u4e3e\uff1a<\/p>\n<p>...<br \/>\n\/\/ Define my benefit level. Employee.BenefitPackage.BenefitPackageLevel myBenefitLevel =<br \/>\nEmployee.BenefitPackage.BenefitPackageLevel.Platinum;<\/p>\n<p>Excellent! At this point, you have been exposed to a number of keywords (and concepts) that allow you to build hierarchies of related types via classical inheritance, containment, and nested types. If the details aren\u2019t crystal clear right now, don\u2019t sweat it. You will be building a number of additional hierarchies over the remainder of this book. Next up, let\u2019s examine the final pillar of OOP: polymorphism.<br \/>\n\u975e\u5e38\u597d\uff01\u6b64\u65f6\uff0c\u60a8\u5df2\u7ecf\u63a5\u89e6\u5230\u8bb8\u591a\u5173\u952e\u5b57\uff08\u548c\u6982\u5ff5\uff09\uff0c\u8fd9\u4e9b\u5173\u952e\u5b57\uff08\u548c\u6982\u5ff5\uff09\u5141\u8bb8\u60a8\u901a\u8fc7\u7ecf\u5178\u7ee7\u627f\u3001\u5305\u542b\u548c\u5d4c\u5957\u7c7b\u578b\u6784\u5efa\u76f8\u5173\u7c7b\u578b\u7684\u5c42\u6b21\u7ed3\u6784\u3002\u5982\u679c\u73b0\u5728\u7ec6\u8282\u8fd8\u4e0d\u6e05\u695a\uff0c\u8bf7\u4e0d\u8981\u62c5\u5fc3\u3002\u60a8\u5c06\u5728\u672c\u4e66\u7684\u5176\u4f59\u90e8\u5206\u6784\u5efa\u8bb8\u591a\u5176\u4ed6\u5c42\u6b21\u7ed3\u6784\u3002\u63a5\u4e0b\u6765\uff0c\u8ba9\u6211\u4eec\u6765\u770b\u770b OOP \u7684\u6700\u540e\u4e00\u4e2a\u652f\u67f1\uff1a\u591a\u6001\u6027\u3002<\/p>\n<h2>Understanding the Third Pillar of OOP: C#\u2019s Polymorphic Support<\/h2>\n<p>\u4e86\u89e3 OOP \u7684\u7b2c\u4e09\u4e2a\u652f\u67f1\uff1aC# \u7684\u591a\u6001\u652f\u6301<\/p>\n<p>Recall that the Employee base class defined a method named GiveBonus(), which was originally implemented as follows (before updating it to use the property pattern):<br \/>\n\u56de\u60f3\u4e00\u4e0b\uff0cEmployee \u57fa\u7c7b\u5b9a\u4e49\u4e86\u4e00\u4e2a\u540d\u4e3a GiveBonus\uff08\uff09 \u7684\u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u6700\u521d\u5b9e\u73b0\u5982\u4e0b\uff08\u5728\u66f4\u65b0\u5b83\u4ee5\u4f7f\u7528\u5c5e\u6027\u6a21\u5f0f\u4e4b\u524d\uff09\uff1a<\/p>\n<p>public partial class Employee<br \/>\n{<br \/>\npublic void GiveBonus(float amount) =&gt; _currPay += amount;<br \/>\n...<br \/>\n}<\/p>\n<p>Because this method has been defined with the public keyword, you can now give bonuses to salespeople and managers (as well as part-time salespeople).<br \/>\n\u7531\u4e8e\u6b64\u65b9\u6cd5\u5df2\u4f7f\u7528\u516c\u5171\u5173\u952e\u5b57\u5b9a\u4e49\uff0c\u56e0\u6b64\u60a8\u73b0\u5728\u53ef\u4ee5\u5411\u9500\u552e\u4eba\u5458\u548c\u7ecf\u7406\uff08\u4ee5\u53ca\u517c\u804c\u9500\u552e\u4eba\u5458\uff09\u63d0\u4f9b\u5956\u91d1\u3002<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> The Employee Class Hierarchy <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>\/\/ Give each employee a bonus?<br \/>\nManager chucky = new Manager(&quot;Chucky&quot;, 50, 92, 100000, &quot;333-23-2322&quot;, 9000); chucky.GiveBonus(300);<br \/>\nchucky.DisplayStats();<br \/>\nConsole.WriteLine();<\/p>\n<p>SalesPerson fran = new SalesPerson(&quot;Fran&quot;, 43, 93, 3000, &quot;932-32-3232&quot;, 31); fran.GiveBonus(200);<br \/>\nfran.DisplayStats();<br \/>\nConsole.ReadLine();<\/p>\n<p>The problem with the current design is that the publicly inherited GiveBonus() method operates identically for all subclasses. Ideally, the bonus of a salesperson or part-time salesperson should consider the number of sales. Perhaps managers should gain additional stock options in conjunction with a monetary bump in salary. Given this, you are suddenly faced with an interesting question: \u201cHow can related types respond differently to the same request?\u201d Again, glad you asked!<br \/>\n\u5f53\u524d\u8bbe\u8ba1\u7684\u95ee\u9898\u5728\u4e8e\uff0c\u516c\u5171\u7ee7\u627f\u7684 GiveBonus\uff08\uff09 \u65b9\u6cd5\u5bf9\u6240\u6709\u5b50\u7c7b\u7684\u64cd\u4f5c\u76f8\u540c\u3002\u7406\u60f3\u60c5\u51b5\u4e0b\uff0c\u9500\u552e\u4eba\u5458\u6216\u517c\u804c\u9500\u552e\u4eba\u5458\u7684\u5956\u91d1\u5e94\u8003\u8651\u9500\u552e\u6570\u91cf\u3002\u4e5f\u8bb8\u7ecf\u7406\u4eba\u5e94\u8be5\u5728\u589e\u52a0\u5de5\u8d44\u7684\u540c\u65f6\u83b7\u5f97\u989d\u5916\u7684\u80a1\u7968\u671f\u6743\u3002\u9274\u4e8e\u6b64\uff0c\u60a8\u7a81\u7136\u9762\u4e34\u4e00\u4e2a\u6709\u8da3\u7684\u95ee\u9898\uff1a\u201c\u76f8\u5173\u7c7b\u578b\u5982\u4f55\u4ee5\u4e0d\u540c\u7684\u65b9\u5f0f\u54cd\u5e94\u540c\u4e00\u8bf7\u6c42\uff1f\u518d\u6b21\uff0c\u5f88\u9ad8\u5174\u4f60\u95ee\uff01<\/p>\n<h2>Using the virtual and override Keywords<\/h2>\n<p>\u4f7f\u7528\u865a\u62df\u5173\u952e\u5b57\u548c\u8986\u76d6\u5173\u952e\u5b57<\/p>\n<p>Polymorphism provides a way for a subclass to define its own version of a method defined by its base class, using the process termed method overriding. To retrofit your current design, you need to understand the meaning of the virtual and override keywords. If a base class wants to define a method that may be (but does not have to be) overridden by a subclass, it must mark the method with the virtual keyword.<br \/>\n\u591a\u6001\u6027\u4e3a\u5b50\u7c7b\u63d0\u4f9b\u4e86\u4e00\u79cd\u65b9\u6cd5\uff0c\u53ef\u4ee5\u4f7f\u7528\u79f0\u4e3a\u65b9\u6cd5\u91cd\u5199\u7684\u8fc7\u7a0b\u6765\u5b9a\u4e49\u5176\u57fa\u7c7b\u5b9a\u4e49\u7684\u65b9\u6cd5\u7684\u81ea\u5df1\u7684\u7248\u672c\u3002\u8981\u6539\u9020\u60a8\u5f53\u524d\u7684\u8bbe\u8ba1\uff0c\u60a8\u9700\u8981\u4e86\u89e3\u865a\u62df\u548c\u8986\u76d6\u5173\u952e\u5b57\u7684\u542b\u4e49\u3002\u5982\u679c\u57fa\u7c7b\u60f3\u8981\u5b9a\u4e49\u4e00\u4e2a\u53ef\u80fd\uff08\u4f46\u4e0d\u5fc5\uff09\u88ab\u5b50\u7c7b\u91cd\u5199\u7684\u65b9\u6cd5\uff0c\u5219\u5fc5\u987b\u4f7f\u7528 virtual \u5173\u952e\u5b57\u6807\u8bb0\u8be5\u65b9\u6cd5\u3002<\/p>\n<p>partial class Employee<br \/>\n{<br \/>\n\/\/ This method can now be &quot;overridden&quot; by a derived class. public virtual void GiveBonus(float amount)<br \/>\n{<br \/>\nPay += amount;<br \/>\n}<br \/>\n...<br \/>\n}<\/p>\n<p>\u25a0 Note  Methods that have been marked with the virtual keyword are (not surprisingly) termed virtual methods.<br \/>\n\u6ce8\u610f \u7528 virtual \u5173\u952e\u5b57\u6807\u8bb0\u7684\u65b9\u6cd5\uff08\u6beb\u4e0d\u5947\u602a\uff09\u79f0\u4e3a\u865a\u62df\u65b9\u6cd5\u3002<\/p>\n<p>When a subclass wants to change the implementation details of a virtual method, it does so using the override keyword. For example, SalesPerson and Manager could override GiveBonus() as follows (assume that PTSalesPerson will not override GiveBonus() and, therefore, simply inherits the version defined by SalesPerson):<br \/>\n\u5f53\u5b50\u7c7b\u60f3\u8981\u66f4\u6539\u865a\u62df\u65b9\u6cd5\u7684\u5b9e\u73b0\u7ec6\u8282\u65f6\uff0c\u5b83\u4f7f\u7528 override \u5173\u952e\u5b57\u6765\u5b9e\u73b0\u3002\u4f8b\u5982\uff0cSalesPerson \u548c Manager \u53ef\u4ee5\u6309\u5982\u4e0b\u65b9\u5f0f\u8986\u76d6 GiveBonus\uff08\uff09\uff08\u5047\u8bbe PTSalesPerson \u4e0d\u4f1a\u8986\u76d6 GiveBonus\uff08\uff09\uff0c\u56e0\u6b64\u53ea\u662f\u7ee7\u627f SalesPerson \u5b9a\u4e49\u7684\u7248\u672c\uff09\uff1a<\/p>\n<p>\/\/SalesPerson.cs namespace Employees;<\/p>\n<p>class SalesPerson : Employee<br \/>\n{<br \/>\n...<br \/>\n\/\/ A salesperson's bonus is influenced by the number of sales. public override void GiveBonus(float amount)<br \/>\n{<br \/>\nint salesBonus = 0;<br \/>\nif (SalesNumber &gt;= 0 &amp;&amp; SalesNumber &lt;= 100)<br \/>\n{<br \/>\nsalesBonus = 10;<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nif (SalesNumber &gt;= 101 &amp;&amp; SalesNumber &lt;= 200)<br \/>\n{<br \/>\nsalesBonus = 15;<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nsalesBonus = 20;<br \/>\n}<br \/>\n}<br \/>\nbase.GiveBonus(amount * salesBonus);<br \/>\n}<br \/>\n}<\/p>\n<p>\/\/Manager.cs namespace Employees;<br \/>\nclass Manager : Employee<br \/>\n{<br \/>\n...<br \/>\npublic override void GiveBonus(float amount)<br \/>\n{<br \/>\nbase.GiveBonus(amount); Random r = new Random(); StockOptions += r.Next(500);<br \/>\n}<br \/>\n}<\/p>\n<p>Notice how each overridden method is free to leverage the default behavior using the base keyword.<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u6bcf\u4e2a\u91cd\u5199\u7684\u65b9\u6cd5\u90fd\u53ef\u4ee5\u4f7f\u7528 base \u5173\u952e\u5b57\u81ea\u7531\u5229\u7528\u9ed8\u8ba4\u884c\u4e3a\u3002<\/p>\n<p>In this way, you have no need to completely reimplement the logic behind GiveBonus() but can reuse (and possibly extend) the default behavior of the parent class.<br \/>\n\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u60a8\u65e0\u9700\u5b8c\u5168\u91cd\u65b0\u5b9e\u73b0 GiveBonus\uff08\uff09 \u80cc\u540e\u7684\u903b\u8f91\uff0c\u4f46\u53ef\u4ee5\u91cd\u7528\uff08\u5e76\u53ef\u80fd\u6269\u5c55\uff09\u7236\u7c7b\u7684\u9ed8\u8ba4\u884c\u4e3a\u3002<\/p>\n<p>Also assume that the current DisplayStats() method of the Employee class has been declared virtually.<br \/>\n\u8fd8\u5047\u8bbe Employee \u7c7b\u7684\u5f53\u524d DisplayStats\uff08\uff09 \u65b9\u6cd5\u5df2\u865a\u62df\u58f0\u660e\u3002<\/p>\n<p>public virtual void DisplayStats()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Name: {0}&quot;, Name);<br \/>\nConsole.WriteLine(&quot;Id: {0}&quot;, Id);<br \/>\nConsole.WriteLine(&quot;Age: {0}&quot;, Age);<br \/>\nConsole.WriteLine(&quot;Pay: {0}&quot;, Pay);<br \/>\nConsole.WriteLine(&quot;SSN: {0}&quot;, SocialSecurityNumber);<br \/>\n}<\/p>\n<p>By doing so, each subclass can override this method to account for displaying the number of sales (for salespeople) and current stock options (for managers). For example, consider Manager\u2019s version of the<br \/>\nDisplayStats() method (the SalesPerson class would implement DisplayStats() in a similar manner to<br \/>\nshow the number of sales).<br \/>\n\u901a\u8fc7\u8fd9\u6837\u505a\uff0c\u6bcf\u4e2a\u5b50\u7c7b\u90fd\u53ef\u4ee5\u91cd\u5199\u6b64\u65b9\u6cd5\uff0c\u4ee5\u663e\u793a\u9500\u552e\u6570\u91cf\uff08\u5bf9\u4e8e\u9500\u552e\u4eba\u5458\uff09\u548c\u5f53\u524d\u80a1\u7968\u671f\u6743\uff08\u5bf9\u4e8e\u7ecf\u7406\uff09\u3002\u4f8b\u5982\uff0c\u8003\u8651\u7ecf\u7406\u7248\u672c\u7684DisplayStats\uff08\uff09 \u65b9\u6cd5\uff08SalesPerson \u7c7b\u5c06\u4ee5\u7c7b\u4f3c\u4e8e\u663e\u793a\u9500\u552e\u6570\u91cf\uff09\u3002<\/p>\n<p>\/\/Manager.cs<br \/>\npublic override void DisplayStats()<br \/>\n{<br \/>\nbase.DisplayStats();<br \/>\nConsole.WriteLine(&quot;Number of Stock Options: {0}&quot;, StockOptions);<br \/>\n}<br \/>\n\/\/SalesPerson.cs<br \/>\npublic override void DisplayStats()<br \/>\n{<br \/>\nbase.DisplayStats();<br \/>\nConsole.WriteLine(&quot;Number of Sales: {0}&quot;, SalesNumber);<br \/>\n}<\/p>\n<p>Now that each subclass can interpret what these virtual methods mean for itself, each object instance behaves as a more independent entity.<br \/>\n\u73b0\u5728\uff0c\u6bcf\u4e2a\u5b50\u7c7b\u90fd\u53ef\u4ee5\u89e3\u91ca\u8fd9\u4e9b\u865a\u62df\u65b9\u6cd5\u5bf9\u81ea\u5df1\u7684\u542b\u4e49\uff0c\u6bcf\u4e2a\u5bf9\u8c61\u5b9e\u4f8b\u7684\u884c\u4e3a\u90fd\u662f\u4e00\u4e2a\u66f4\u52a0\u72ec\u7acb\u7684\u5b9e\u4f53\u3002<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> The Employee Class Hierarchy <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>\/\/ A better bonus system!<br \/>\nManager chucky = new Manager(&quot;Chucky&quot;, 50, 92, 100000, &quot;333-23-2322&quot;, 9000); chucky.GiveBonus(300);<br \/>\nchucky.DisplayStats();<br \/>\nConsole.WriteLine();<\/p>\n<p>SalesPerson fran = new SalesPerson(&quot;Fran&quot;, 43, 93, 3000, &quot;932-32-3232&quot;, 31); fran.GiveBonus(200);<br \/>\nfran.DisplayStats();<br \/>\nConsole.ReadLine();<\/p>\n<p>The following output shows a possible test run of your application thus far:<br \/>\n\u4ee5\u4e0b\u8f93\u51fa\u663e\u793a\u4e86\u5230\u76ee\u524d\u4e3a\u6b62\u5e94\u7528\u7a0b\u5e8f\u53ef\u80fd\u7684\u6d4b\u8bd5\u8fd0\u884c\uff1a<\/p>\n<p><strong><strong><em> The Employee Class Hierarchy <\/em><\/strong><\/strong> Name: Chucky<br \/>\nID: 92<br \/>\nAge: 50<br \/>\nPay: 100300<br \/>\nSSN: 333-23-2322<br \/>\nNumber of Stock Options: 9337<\/p>\n<p>Name: Fran ID: 93<br \/>\nAge: 43<br \/>\nPay: 5000<br \/>\nSSN: 932-32-3232<br \/>\nNumber of Sales: 31<\/p>\n<h2>Overriding Virtual Members with Visual Studio\/Visual Studio Code<\/h2>\n<p>\u4f7f\u7528 Visual Studio\/Visual Studio \u4ee3\u7801\u8986\u76d6\u865a\u62df\u6210\u5458<\/p>\n<p>As you might have already noticed, when you are overriding a member, you must recall the type of every parameter\u2014not to mention the method name and parameter-passing conventions (ref, out, and params). Both Visual Studio and Visual Studio Code have a helpful feature that you can make use of when overriding a virtual member. If you type the word override within the scope of a class type (then hit the spacebar), IntelliSense will automatically display a list of all the overridable members defined in your parent classes, excluding methods already overridden.<br \/>\n\u60a8\u53ef\u80fd\u5df2\u7ecf\u6ce8\u610f\u5230\uff0c\u5728\u91cd\u5199\u6210\u5458\u65f6\uff0c\u5fc5\u987b\u8c03\u7528\u6bcf\u4e2a\u53c2\u6570\u7684\u7c7b\u578b\uff0c\u66f4\u4e0d\u7528\u8bf4\u65b9\u6cd5\u540d\u79f0\u548c\u53c2\u6570\u4f20\u9012\u7ea6\u5b9a\uff08ref\u3001out \u548c\u53c2\u6570\uff09\u3002Visual Studio \u548c Visual Studio Code \u90fd\u6709\u4e00\u4e2a\u6709\u7528\u7684\u529f\u80fd\uff0c\u60a8\u53ef\u4ee5\u5728\u8986\u76d6\u865a\u62df\u6210\u5458\u65f6\u4f7f\u7528\u8be5\u529f\u80fd\u3002\u5982\u679c\u5728\u7c7b\u7c7b\u578b\u7684\u8303\u56f4\u5185\u952e\u5165\u5355\u8bcd\u91cd\u5199\uff08\u7136\u540e\u6309\u7a7a\u683c\u952e\uff09\uff0cIntelliSense \u5c06\u81ea\u52a8\u663e\u793a\u7236\u7c7b\u4e2d\u5b9a\u4e49\u7684\u6240\u6709\u53ef\u91cd\u5199\u6210\u5458\u7684\u5217\u8868\uff0c\u4e0d\u5305\u62ec\u5df2\u91cd\u5199\u7684\u65b9\u6cd5\u3002<\/p>\n<p>When you select a member and hit the Enter key, the IDE responds by automatically filling in the method stub on your behalf. Note that you also receive a code statement that calls your parent\u2019s version of the virtual member (you are free to delete this line if it is not required). For example, if you used this<br \/>\ntechnique when overriding the DisplayStats() method, you might find the following autogenerated code:<br \/>\n\u9009\u62e9\u6210\u5458\u5e76\u6309 Enter \u952e\u65f6\uff0cIDE \u5c06\u901a\u8fc7\u4ee3\u8868\u60a8\u81ea\u52a8\u586b\u5199\u65b9\u6cd5\u5b58\u6839\u6765\u54cd\u5e94\u3002\u8bf7\u6ce8\u610f\uff0c\u60a8\u8fd8\u4f1a\u6536\u5230\u4e00\u4e2a\u4ee3\u7801\u8bed\u53e5\uff0c\u8be5\u8bed\u53e5\u8c03\u7528\u60a8\u7236\u7ea7\u7248\u672c\u7684\u865a\u62df\u6210\u5458\uff08\u5982\u679c\u4e0d\u9700\u8981\uff0c\u60a8\u53ef\u4ee5\u81ea\u7531\u5220\u9664\u6b64\u884c\uff09\u3002\u4f8b\u5982\uff0c\u5982\u679c\u60a8\u4f7f\u7528\u4e86\u8fd9\u4e2a\u5728\u91cd\u5199 DisplayStats\uff08\uff09 \u65b9\u6cd5\u65f6\uff0c\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u4ee5\u4e0b\u81ea\u52a8\u751f\u6210\u7684\u4ee3\u7801\uff1a<\/p>\n<p>public override void DisplayStats()<br \/>\n{<br \/>\nbase.DisplayStats();<br \/>\n}<\/p>\n<h2>Sealing Virtual Members (Updated 10.0)<\/h2>\n<p>\u5bc6\u5c01\u865a\u62df\u6210\u5458\uff0810.0 \u66f4\u65b0\uff09<\/p>\n<p>Recall that the sealed keyword can be applied to a class type to prevent other types from extending its behavior via inheritance. As you might remember, you sealed PtSalesPerson because you assumed it made no sense for other developers to extend this line of inheritance any further.<br \/>\n\u56de\u60f3\u4e00\u4e0b\uff0c\u5bc6\u5c01\u5173\u952e\u5b57\u53ef\u4ee5\u5e94\u7528\u4e8e\u7c7b\u7c7b\u578b\uff0c\u4ee5\u9632\u6b62\u5176\u4ed6\u7c7b\u578b\u901a\u8fc7\u7ee7\u627f\u6269\u5c55\u5176\u884c\u4e3a\u3002\u60a8\u53ef\u80fd\u8fd8\u8bb0\u5f97\uff0c\u60a8\u5bc6\u5c01\u4e86 PtSalesPerson\uff0c\u56e0\u4e3a\u60a8\u8ba4\u4e3a\u5176\u4ed6\u5f00\u53d1\u4eba\u5458\u8fdb\u4e00\u6b65\u6269\u5c55\u8fd9\u6761\u7ee7\u627f\u7ebf\u662f\u6ca1\u6709\u610f\u4e49\u7684\u3002<\/p>\n<p>On a related note, sometimes you might not want to seal an entire class but simply want to prevent derived types from overriding particular virtual methods. For example, assume you do not want part-time salespeople to obtain customized bonuses. To prevent the PTSalesPerson class from overriding the virtual GiveBonus() method, you could effectively seal this method in the SalesPerson class as follows:<br \/>\n\u5728\u76f8\u5173\u7684\u8bf4\u660e\u4e2d\uff0c\u6709\u65f6\u60a8\u53ef\u80fd\u4e0d\u60f3\u5bc6\u5c01\u6574\u4e2a\u7c7b\uff0c\u800c\u53ea\u662f\u5e0c\u671b\u9632\u6b62\u6d3e\u751f\u7c7b\u578b\u91cd\u5199\u7279\u5b9a\u7684\u865a\u62df\u65b9\u6cd5\u3002\u4f8b\u5982\uff0c\u5047\u8bbe\u60a8\u4e0d\u5e0c\u671b\u517c\u804c\u9500\u552e\u4eba\u5458\u83b7\u5f97\u5b9a\u5236\u7684\u5956\u91d1\u3002\u4e3a\u4e86\u9632\u6b62 PTSalesPerson \u7c7b\u91cd\u5199\u865a\u62df GiveBonus\uff08\uff09 \u65b9\u6cd5\uff0c\u60a8\u53ef\u4ee5\u6709\u6548\u5730\u5c06\u6b64\u65b9\u6cd5\u5bc6\u5c01\u5728 SalesPerson \u7c7b\u4e2d\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>\/\/ SalesPerson has sealed the GiveBonus() method! class SalesPerson : Employee<br \/>\n{<br \/>\n...<br \/>\npublic override sealed void GiveBonus(float amount)<br \/>\n{<br \/>\n...<br \/>\n}<br \/>\n}<\/p>\n<p>Here, SalesPerson has indeed overridden the virtual GiveBonus() method defined in the Employee class; however, it has explicitly marked it as sealed. Thus, if you attempted to override this method in the PtSalesPerson class, you would receive compile-time errors, as shown in the following code:<br \/>\n\u5728\u8fd9\u91cc\uff0c\u9500\u552e\u4eba\u5458\u786e\u5b9e\u8986\u76d6\u4e86 Employee \u7c7b\u4e2d\u5b9a\u4e49\u7684\u865a\u62df GiveBonus\uff08\uff09 \u65b9\u6cd5;\u4f46\u662f\uff0c\u5b83\u5df2\u660e\u786e\u5c06\u5176\u6807\u8bb0\u4e3a\u5bc6\u5c01\u3002\u56e0\u6b64\uff0c\u5982\u679c\u60a8\u5c1d\u8bd5\u5728 PtSalesPerson \u7c7b\u4e2d\u91cd\u5199\u6b64\u65b9\u6cd5\uff0c\u60a8\u5c06\u6536\u5230\u7f16\u8bd1\u65f6\u9519\u8bef\uff0c\u5982\u4ee5\u4e0b\u4ee3\u7801\u6240\u793a\uff1a<\/p>\n<p>sealed class PTSalesPerson : SalesPerson<br \/>\n{<br \/>\n...<br \/>\n\/\/ Compiler error! Can't override this method<br \/>\n\/\/ in the PTSalesPerson class, as it was sealed. public override void GiveBonus(float amount)<br \/>\n{<br \/>\n}<br \/>\n}<\/p>\n<p>New in C# 10, the ToString() method for a record can be sealed, preventing the compiler from synthesizing a ToString() method for any derived record types. Returning to the CarRecord from Chapter 5, notice the sealed ToString() method:<br \/>\nC# 10 \u4e2d\u7684\u65b0\u589e\u529f\u80fd\u662f\uff0c\u53ef\u4ee5\u5bc6\u5c01\u8bb0\u5f55\u7684 ToString\uff08\uff09 \u65b9\u6cd5\uff0c\u4ece\u800c\u9632\u6b62\u7f16\u8bd1\u5668\u4e3a\u4efb\u4f55\u6d3e\u751f\u8bb0\u5f55\u7c7b\u578b\u5408\u6210 ToString\uff08\uff09 \u65b9\u6cd5\u3002<\/p>\n<p>public record CarRecord<br \/>\n{<br \/>\npublic string Make { get; init; } public string Model { get; init; } public string Color { get; init; }<\/p>\n<p>public CarRecord() {}<br \/>\npublic CarRecord(string make, string model, string color)<br \/>\n{<br \/>\nMake = make;<br \/>\nModel = model;<br \/>\nColor = color;<br \/>\n}<br \/>\npublic sealed override string ToString() =&gt; $&quot;The is a {Color} {Make} {Model}&quot;;<br \/>\n}<\/p>\n<h2>Understanding Abstract Classes<\/h2>\n<p>\u4e86\u89e3\u62bd\u8c61\u7c7b<\/p>\n<p>Currently, the Employee base class has been designed to supply various data members for its descendants, as well as supply two virtual methods (GiveBonus() and DisplayStats()) that may be overridden by a given descendant. While this is all well and good, there is a rather odd byproduct of the current design; you can directly create instances of the Employee base class.<br \/>\n\u76ee\u524d\uff0cEmployee \u57fa\u7c7b\u88ab\u8bbe\u8ba1\u4e3a\u4e3a\u5176\u540e\u4ee3\u63d0\u4f9b\u5404\u79cd\u6570\u636e\u6210\u5458\uff0c\u5e76\u63d0\u4f9b\u4e24\u4e2a\u53ef\u80fd\u88ab\u7ed9\u5b9a\u540e\u4ee3\u8986\u76d6\u7684\u865a\u62df\u65b9\u6cd5\uff08GiveBonus\uff08\uff09 \u548c DisplayStats\uff08\uff09\uff09\u3002\u867d\u7136\u8fd9\u4e00\u5207\u90fd\u5f88\u597d\uff0c\u4f46\u5f53\u524d\u8bbe\u8ba1\u6709\u4e00\u4e2a\u76f8\u5f53\u5947\u602a\u7684\u526f\u4ea7\u54c1;\u53ef\u4ee5\u76f4\u63a5\u521b\u5efa Employee \u57fa\u7c7b\u7684\u5b9e\u4f8b\u3002<\/p>\n<p>\/\/ What exactly does this mean? Employee X = new Employee();<\/p>\n<p>In this example, the only real purpose of the Employee base class is to define common members for all subclasses. In all likelihood, you did not intend anyone to create a direct instance of this class, reason being that the Employee type itself is too general of a concept. For example, if I were to walk up to you and say \u201cI\u2019m an employee,\u201d I would bet your first question to me would be \u201cWhat kind of employee are you? Are you a consultant, trainer, admin assistant, copy editor, or White House aide?\u201d<br \/>\n\u5728\u6b64\u793a\u4f8b\u4e2d\uff0cEmployee \u57fa\u7c7b\u7684\u552f\u4e00\u5b9e\u9645\u7528\u9014\u662f\u4e3a\u6240\u6709\u5b50\u7c7b\u5b9a\u4e49\u516c\u5171\u6210\u5458\u3002\u60a8\u5f88\u53ef\u80fd\u4e0d\u6253\u7b97\u8ba9\u4efb\u4f55\u4eba\u521b\u5efa\u6b64\u7c7b\u7684\u76f4\u63a5\u5b9e\u4f8b\uff0c\u539f\u56e0\u662f Employee \u7c7b\u578b\u672c\u8eab\u7684\u6982\u5ff5\u8fc7\u4e8e\u7b3c\u7edf\u3002\u4f8b\u5982\uff0c\u5982\u679c\u6211\u8d70\u5230\u4f60\u9762\u524d\u8bf4\u201c\u6211\u662f\u4e00\u540d\u5458\u5de5\u201d\uff0c\u6211\u6562\u6253\u8d4c\u4f60\u95ee\u6211\u7684\u7b2c\u4e00\u4e2a\u95ee\u9898\u662f\u201c\u4f60\u662f\u4ec0\u4e48\u6837\u7684\u5458\u5de5\uff1f\u4f60\u662f\u987e\u95ee\u3001\u57f9\u8bad\u5e08\u3001\u884c\u653f\u52a9\u7406\u3001\u6587\u6848\u7f16\u8f91\u8fd8\u662f\u767d\u5bab\u52a9\u624b\uff1f\u201d<\/p>\n<p>Given that many base classes tend to be rather nebulous entities, a far better design for this example is to prevent the ability to directly create a new Employee object in code. In C#, you can enforce this<br \/>\nprogrammatically by using the abstract keyword in the class definition, thus creating an abstract base class.<br \/>\n\u9274\u4e8e\u8bb8\u591a\u57fa\u7c7b\u5f80\u5f80\u662f\u76f8\u5f53\u6a21\u7cca\u7684\u5b9e\u4f53\uff0c\u6b64\u793a\u4f8b\u7684\u66f4\u597d\u8bbe\u8ba1\u662f\u9632\u6b62\u5728\u4ee3\u7801\u4e2d\u76f4\u63a5\u521b\u5efa\u65b0\u7684 Employee \u5bf9\u8c61\u3002\u5728 C# \u4e2d\uff0c\u53ef\u4ee5\u5f3a\u5236\u6267\u884c\u6b64\u64cd\u4f5c\u4ee5\u7f16\u7a0b\u65b9\u5f0f\u5728\u7c7b\u5b9a\u4e49\u4e2d\u4f7f\u7528 abstract \u5173\u952e\u5b57\uff0c\u4ece\u800c\u521b\u5efa\u62bd\u8c61\u57fa\u7c7b\u3002<\/p>\n<p>\/\/ Update the Employee class as abstract<br \/>\n\/\/ to prevent direct instantiation. abstract partial class Employee<br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>With this, if you now attempt to create an instance of the Employee class, you are issued a compile- time error.<br \/>\n\u8fd9\u6837\uff0c\u5982\u679c\u60a8\u73b0\u5728\u5c1d\u8bd5\u521b\u5efa Employee \u7c7b\u7684\u5b9e\u4f8b\uff0c\u5219\u4f1a\u53d1\u51fa\u7f16\u8bd1\u65f6\u9519\u8bef\u3002<\/p>\n<p>\/\/ Error! Cannot create an instance of an abstract class! Employee X = new Employee();<\/p>\n<p>At first glance, it might seem strange to define a class that you cannot directly create an instance of. Recall, however, that base classes (abstract or not) are useful, in that they contain all the common data and functionality of derived types. Using this form of abstraction, you are able to model that the \u201cidea\u201d of an employee is completely valid; it is just not a concrete entity. Also understand that although you cannot directly create an instance of an abstract class, it is still assembled in memory when derived classes are created. Thus, it is perfectly fine (and common) for abstract classes to define any number of constructors that are called indirectly when derived classes are allocated.<br \/>\n\u4e4d\u4e00\u770b\uff0c\u5b9a\u4e49\u4e00\u4e2a\u4e0d\u80fd\u76f4\u63a5\u521b\u5efa\u5b9e\u4f8b\u7684\u7c7b\u4f3c\u4e4e\u5f88\u5947\u602a\u3002\u4f46\u662f\uff0c\u8bf7\u8bb0\u4f4f\uff0c\u57fa\u7c7b\uff08\u62bd\u8c61\u6216\u975e\u62bd\u8c61\uff09\u662f\u6709\u7528\u7684\uff0c\u56e0\u4e3a\u5b83\u4eec\u5305\u542b\u6d3e\u751f\u7c7b\u578b\u7684\u6240\u6709\u901a\u7528\u6570\u636e\u548c\u529f\u80fd\u3002\u4f7f\u7528\u8fd9\u79cd\u62bd\u8c61\u5f62\u5f0f\uff0c\u60a8\u53ef\u4ee5\u6a21\u62df\u5458\u5de5\u7684\u201c\u60f3\u6cd5\u201d\u662f\u5b8c\u5168\u6709\u6548\u7684;\u5b83\u53ea\u662f\u4e0d\u662f\u4e00\u4e2a\u5177\u4f53\u7684\u5b9e\u4f53\u3002\u8fd8\u8981\u4e86\u89e3\uff0c\u5c3d\u7ba1\u4e0d\u80fd\u76f4\u63a5\u521b\u5efa\u62bd\u8c61\u7c7b\u7684\u5b9e\u4f8b\uff0c\u4f46\u5728\u521b\u5efa\u6d3e\u751f\u7c7b\u65f6\uff0c\u5b83\u4ecd\u7136\u5728\u5185\u5b58\u4e2d\u7ec4\u88c5\u3002\u56e0\u6b64\uff0c\u62bd\u8c61\u7c7b\u5b9a\u4e49\u4efb\u610f\u6570\u91cf\u7684\u6784\u9020\u51fd\u6570\u662f\u5b8c\u5168\u53ef\u4ee5\u7684\uff08\u4e5f\u5f88\u5e38\u89c1\uff09\uff0c\u8fd9\u4e9b\u6784\u9020\u51fd\u6570\u5728\u5206\u914d\u6d3e\u751f\u7c7b\u65f6\u95f4\u63a5\u8c03\u7528\u3002<\/p>\n<p>At this point, you have constructed a fairly interesting employee hierarchy. You will add a bit more functionality to this application later in this chapter when examining C# casting rules. Until then, Figure 6-4 illustrates the crux of your current design.<br \/>\n\u81f3\u6b64\uff0c\u60a8\u5df2\u7ecf\u6784\u5efa\u4e86\u4e00\u4e2a\u76f8\u5f53\u6709\u8da3\u7684\u5458\u5de5\u5c42\u6b21\u7ed3\u6784\u3002\u5728\u672c\u7ae0\u540e\u9762\u7684\u68c0\u67e5 C# \u8f6c\u6362\u89c4\u5219\u65f6\uff0c\u60a8\u5c06\u5411\u6b64\u5e94\u7528\u7a0b\u5e8f\u6dfb\u52a0\u66f4\u591a\u529f\u80fd\u3002\u5728\u6b64\u4e4b\u524d\uff0c\u56fe 6-4 \u8bf4\u660e\u4e86\u5f53\u524d\u8bbe\u8ba1\u7684\u5173\u952e\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/0604.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Figure 6-4. The employee hierarchy<br \/>\n\u56fe 6-4\u3002 \u5458\u5de5\u5c42\u6b21\u7ed3\u6784<\/p>\n<h2>Understanding the Polymorphic Interface<\/h2>\n<p>\u4e86\u89e3\u591a\u6001\u754c\u9762<\/p>\n<p>When a class has been defined as an abstract base class (via the abstract keyword), it may define any number of abstract members. Abstract members can be used whenever you want to define a member that does not supply a default implementation but must be accounted for by each derived class. By doing so, you enforce a polymorphic interface on each descendant, leaving them to contend with the task of providing the details behind your abstract methods.<br \/>\n\u5f53\u4e00\u4e2a\u7c7b\u88ab\u5b9a\u4e49\u4e3a\u62bd\u8c61\u57fa\u7c7b\uff08\u901a\u8fc7\u62bd\u8c61\u5173\u952e\u5b57\uff09\u65f6\uff0c\u5b83\u53ef\u4ee5\u5b9a\u4e49\u4efb\u610f\u6570\u91cf\u7684\u62bd\u8c61\u6210\u5458\u3002\u6bcf\u5f53\u8981\u5b9a\u4e49\u4e0d\u63d0\u4f9b\u9ed8\u8ba4\u5b9e\u73b0\u4f46\u5fc5\u987b\u7531\u6bcf\u4e2a\u6d3e\u751f\u7c7b\u8003\u8651\u7684\u6210\u5458\u65f6\uff0c\u90fd\u53ef\u4ee5\u4f7f\u7528\u62bd\u8c61\u6210\u5458\u3002\u901a\u8fc7\u8fd9\u6837\u505a\uff0c\u60a8\u53ef\u4ee5\u5728\u6bcf\u4e2a\u540e\u4ee3\u4e0a\u5f3a\u5236\u5b9e\u65bd\u591a\u6001\u63a5\u53e3\uff0c\u8ba9\u4ed6\u4eec\u5e94\u5bf9\u63d0\u4f9b\u62bd\u8c61\u65b9\u6cd5\u80cc\u540e\u7684\u7ec6\u8282\u7684\u4efb\u52a1\u3002<\/p>\n<p>Simply put, an abstract base class\u2019s polymorphic interface simply refers to its set of virtual and abstract methods. This is much more interesting than first meets the eye because this trait of OOP allows you to build easily extendable and flexible software applications. To illustrate, you will be implementing (and slightly modifying) the hierarchy of shapes briefly examined in Chapter 5 during the overview of the pillars of OOP. To begin, create a new C# Console Application project named Shapes.<br \/>\n\u7b80\u5355\u5730\u8bf4\uff0c\u62bd\u8c61\u57fa\u7c7b\u7684\u591a\u6001\u63a5\u53e3\u53ea\u662f\u6307\u4ee3\u5b83\u7684\u865a\u62df\u548c\u62bd\u8c61\u65b9\u6cd5\u96c6\u3002\u8fd9\u6bd4\u7b2c\u4e00\u6b21\u770b\u5230\u7684\u8981\u6709\u8da3\u5f97\u591a\uff0c\u56e0\u4e3aOOP\u7684\u8fd9\u79cd\u7279\u6027\u5141\u8bb8\u60a8\u6784\u5efa\u6613\u4e8e\u6269\u5c55\u548c\u7075\u6d3b\u7684\u8f6f\u4ef6\u5e94\u7528\u7a0b\u5e8f\u3002\u4e3a\u4e86\u8bf4\u660e\u8fd9\u4e00\u70b9\uff0c\u60a8\u5c06\u5b9e\u73b0\uff08\u5e76\u7a0d\u5fae\u4fee\u6539\uff09\u7b2c 5 \u7ae0\u4e2d\u7b80\u8981\u68c0\u67e5\u7684\u5f62\u72b6\u5c42\u6b21\u7ed3\u6784 \uff0c\u5728\u6982\u8ff0OOP\u3002\u9996\u5148\uff0c\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a Shapes \u7684\u65b0 C# \u63a7\u5236\u53f0\u5e94\u7528\u7a0b\u5e8f\u9879\u76ee\u3002<br \/>\nIn Figure 6-5, notice that the Hexagon and Circle types each extend the Shape base class. Like any base class, Shape defines a number of members (a PetName property and Draw() method, in this case) that are common to all descendants.<br \/>\n\u5728\u56fe 6-5 \u4e2d\uff0c\u8bf7\u6ce8\u610f\uff0c\u516d\u8fb9\u5f62\u548c\u5706\u5f62\u7c7b\u578b\u5206\u522b\u6269\u5c55\u4e86 Shape \u57fa\u7c7b\u3002\u4e0e\u4efb\u4f55\u57fa\u7c7b\u4e00\u6837\uff0cShape \u5b9a\u4e49\u4e86\u6240\u6709\u540e\u4ee3\u5171\u6709\u7684\u8bb8\u591a\u6210\u5458\uff08\u5728\u672c\u4f8b\u4e2d\u4e3a PetName \u5c5e\u6027\u548c Draw\uff08\uff09 \u65b9\u6cd5\uff09\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/0605.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Figure 6-5. The shapes hierarchy<br \/>\n\u56fe 6-5\u3002 \u5f62\u72b6\u5c42\u6b21\u7ed3\u6784<\/p>\n<p>Much like the employee hierarchy, you should be able to tell that you don\u2019t want to allow the object user to create an instance of Shape directly, as it is too abstract of a concept. Again, to prevent the direct creation of the Shape type, you could define it as an abstract class. As well, given that you want the derived types<br \/>\nto respond uniquely to the Draw() method, let\u2019s mark it as virtual and define a default implementation. Notice that the constructor is marked as protected so it can be called only from derived classes.<br \/>\n\u4e0e\u5458\u5de5\u5c42\u6b21\u7ed3\u6784\u975e\u5e38\u76f8\u4f3c\uff0c\u60a8\u5e94\u8be5\u80fd\u591f\u5224\u65ad\u51fa\u60a8\u4e0d\u5e0c\u671b\u5141\u8bb8\u5bf9\u8c61\u7528\u6237\u76f4\u63a5\u521b\u5efa Shape \u7684\u5b9e\u4f8b\uff0c\u56e0\u4e3a\u5b83\u7684\u6982\u5ff5\u592a\u62bd\u8c61\u4e86\u3002\u540c\u6837\uff0c\u4e3a\u4e86\u9632\u6b62\u76f4\u63a5\u521b\u5efa Shape \u7c7b\u578b\uff0c\u53ef\u4ee5\u5c06\u5176\u5b9a\u4e49\u4e3a\u62bd\u8c61\u7c7b\u3002\u540c\u6837\uff0c\u9274\u4e8e\u60a8\u9700\u8981\u6d3e\u751f\u7c7b\u578b\u4e3a\u4e86\u552f\u4e00\u5730\u54cd\u5e94 Draw\uff08\uff09 \u65b9\u6cd5\uff0c\u8ba9\u6211\u4eec\u5c06\u5176\u6807\u8bb0\u4e3a\u865a\u62df\u5e76\u5b9a\u4e49\u4e00\u4e2a\u9ed8\u8ba4\u5b9e\u73b0\u3002\u8bf7\u6ce8\u610f\uff0c\u6784\u9020\u51fd\u6570\u88ab\u6807\u8bb0\u4e3a\u53d7\u4fdd\u62a4\uff0c\u56e0\u6b64\u53ea\u80fd\u4ece\u6d3e\u751f\u7c7b\u8c03\u7528\u5b83\u3002<\/p>\n<p>\/\/ The abstract base class of the hierarchy. namespace Shapes;<br \/>\nabstract class Shape<br \/>\n{<br \/>\nprotected Shape(string name = &quot;NoName&quot;)<br \/>\n{<br \/>\nPetName = name;<br \/>\n}<\/p>\n<p>public string PetName { get; set; }<\/p>\n<p>\/\/ A single virtual method. public virtual void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Inside Shape.Draw()&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Notice that the virtual Draw() method provides a default implementation that simply prints out a message that informs you that you are calling the Draw() method within the Shape base class. Now recall that when a method is marked with the virtual keyword, the method provides a default implementation that all derived types automatically inherit. If a child class so chooses, it may override the method but does not have to. Given this, consider the following implementation of the Circle and Hexagon types:<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u865a\u62df Draw\uff08\uff09 \u65b9\u6cd5\u63d0\u4f9b\u4e86\u4e00\u4e2a\u9ed8\u8ba4\u5b9e\u73b0\uff0c\u8be5\u5b9e\u73b0\u53ea\u662f\u6253\u5370\u51fa\u4e00\u6761\u6d88\u606f\uff0c\u901a\u77e5\u60a8\u6b63\u5728 Shape \u57fa\u7c7b\u4e2d\u8c03\u7528 Draw\uff08\uff09 \u65b9\u6cd5\u3002 \u73b0\u5728\u56de\u60f3\u4e00\u4e0b\uff0c\u5f53\u4e00\u4e2a\u65b9\u6cd5\u88ab\u6807\u8bb0\u4e3a virtual \u5173\u952e\u5b57\u65f6\uff0c\u8be5\u65b9\u6cd5\u4f1a\u63d0\u4f9b\u4e00\u4e2a\u6240\u6709\u6d3e\u751f\u7c7b\u578b\u90fd\u4f1a\u81ea\u52a8\u7ee7\u627f\u7684\u9ed8\u8ba4\u5b9e\u73b0\u3002\u5982\u679c\u5b50\u7c7b\u9009\u62e9\u8fd9\u6837\u505a\uff0c\u5b83\u53ef\u4ee5\u91cd\u5199\u8be5\u65b9\u6cd5\uff0c\u4f46\u4e0d\u5fc5\u91cd\u5199\u3002\u9274\u4e8e\u6b64\uff0c\u8bf7\u8003\u8651\u4ee5\u4e0b\u5706\u5f62\u548c\u516d\u8fb9\u5f62\u7c7b\u578b\u7684\u5b9e\u73b0\uff1a<\/p>\n<p>\/\/Circle.cs namespace Shapes;<br \/>\n\/\/ Circle DOES NOT override Draw(). class Circle : Shape<br \/>\n{<br \/>\npublic Circle() {}<br \/>\npublic Circle(string name) : base(name){}<br \/>\n}<\/p>\n<p>\/\/Hexagon.cs namespace Shapes;<br \/>\n\/\/ Hexagon DOES override Draw(). class Hexagon : Shape<br \/>\n{<br \/>\npublic Hexagon() {}<br \/>\npublic Hexagon(string name) : base(name){} public override void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing {0} the Hexagon&quot;, PetName);<br \/>\n}<br \/>\n}<\/p>\n<p>The usefulness of abstract methods becomes crystal clear when you once again remember that subclasses are never required to override virtual methods (as in the case of Circle). Therefore, if you create an instance of the Hexagon and Circle types, you\u2019d find that Hexagon understands how to \u201cdraw\u201d itself correctly or at least print out an appropriate message to the console. Circle, however, is more than a bit confused.<br \/>\n\u5f53\u4f60\u518d\u6b21\u8bb0\u4f4f\u5b50\u7c7b\u6c38\u8fdc\u4e0d\u9700\u8981\u8986\u76d6\u865a\u62df\u65b9\u6cd5\uff08\u5982 Circle \u7684\u60c5\u51b5\uff09\u65f6\uff0c\u62bd\u8c61\u65b9\u6cd5\u7684\u6709\u7528\u6027\u53d8\u5f97\u975e\u5e38\u660e\u663e\u3002\u56e0\u6b64\uff0c\u5982\u679c\u60a8\u521b\u5efa Hexagon \u548c Circle \u7c7b\u578b\u7684\u5b9e\u4f8b\uff0c\u60a8\u4f1a\u53d1\u73b0 Hexagon \u77e5\u9053\u5982\u4f55\u6b63\u786e\u201c\u7ed8\u5236\u201d\u81ea\u8eab\u6216\u81f3\u5c11\u5c06\u9002\u5f53\u7684\u6d88\u606f\u6253\u5370\u5230\u63a7\u5236\u53f0\u3002\u7136\u800c\uff0cCircle\u5374\u6709\u70b9\u56f0\u60d1\u3002<\/p>\n<p>using Shapes;<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fun with Polymorphism <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>Hexagon hex = new Hexagon(&quot;Beth&quot;); hex.Draw();<br \/>\nCircle cir = new Circle(&quot;Cindy&quot;);<br \/>\n\/\/ Calls base class implementation! cir.Draw();<br \/>\nConsole.ReadLine();<\/p>\n<p>Now consider the following output of the previous code:<br \/>\n\u73b0\u5728\u8003\u8651\u524d\u9762\u4ee3\u7801\u7684\u4ee5\u4e0b\u8f93\u51fa\uff1a<\/p>\n<p><strong><strong><em> Fun with Polymorphism <\/em><\/strong><\/strong> Drawing Beth the Hexagon<br \/>\nInside Shape.Draw()<\/p>\n<p>Clearly, this is not an intelligent design for the current hierarchy. To force each child class to override the Draw() method, you can define Draw() as an abstract method of the Shape class, which by definition means you provide no default implementation whatsoever. To mark a method as abstract in C#, you use the abstract keyword. Notice that abstract members do not provide any implementation whatsoever.<br \/>\n\u663e\u7136\uff0c\u8fd9\u4e0d\u662f\u5f53\u524d\u5c42\u6b21\u7ed3\u6784\u7684\u667a\u80fd\u8bbe\u8ba1\u3002\u82e5\u8981\u5f3a\u5236\u6bcf\u4e2a\u5b50\u7c7b\u91cd\u5199 Draw\uff08\uff09 \u65b9\u6cd5\uff0c\u53ef\u4ee5\u5c06 Draw\uff08\uff09 \u5b9a\u4e49\u4e3a Shape \u7c7b\u7684\u62bd\u8c61\u65b9\u6cd5\uff0c\u6839\u636e\u5b9a\u4e49\uff0c\u8fd9\u610f\u5473\u7740\u60a8\u4e0d\u63d0\u4f9b\u4efb\u4f55\u9ed8\u8ba4\u5b9e\u73b0\u3002 \u82e5\u8981\u5728 C# \u4e2d\u5c06\u65b9\u6cd5\u6807\u8bb0\u4e3a\u62bd\u8c61\uff0c\u8bf7\u4f7f\u7528 abstract \u5173\u952e\u5b57\u3002\u8bf7\u6ce8\u610f\uff0c\u62bd\u8c61\u6210\u5458\u4e0d\u63d0\u4f9b\u4efb\u4f55\u5b9e\u73b0\u3002<\/p>\n<p>abstract class Shape<br \/>\n{<br \/>\n\/\/ Force all child classes to define how to be rendered. public abstract void Draw();<br \/>\n...<br \/>\n}<\/p>\n<p>\u25a0 Note Abstract methods can be defined only in abstract classes. If you attempt to do otherwise, you will be issued a compiler error.<br \/>\n\u6ce8\u610f \u62bd\u8c61\u65b9\u6cd5\u53ea\u80fd\u5728\u62bd\u8c61\u7c7b\u4e2d\u5b9a\u4e49\u3002\u5982\u679c\u5c1d\u8bd5\u4e0d\u8fd9\u6837\u505a\uff0c\u5c06\u5411\u60a8\u53d1\u51fa\u7f16\u8bd1\u5668\u9519\u8bef\u3002<\/p>\n<p>Methods marked with abstract are pure protocol. They simply define the name, return type (if any), and parameter set (if required). Here, the abstract Shape class informs the derived types that \u201cI have a method named Draw() that takes no arguments and returns nothing. If you derive from me, you figure out the details.\u201d<br \/>\n\u7528\u62bd\u8c61\u6807\u8bb0\u7684\u65b9\u6cd5\u7eaf\u534f\u8bae\u3002\u5b83\u4eec\u53ea\u662f\u5b9a\u4e49\u540d\u79f0\u3001\u8fd4\u56de\u7c7b\u578b\uff08\u5982\u679c\u6709\uff09\u548c\u53c2\u6570\u96c6\uff08\u5982\u679c\u9700\u8981\uff09\u3002\u5728\u8fd9\u91cc\uff0c\u62bd\u8c61\u7684 Shape \u7c7b\u901a\u77e5\u6d3e\u751f\u7c7b\u578b\u201c\u6211\u6709\u4e00\u4e2a\u540d\u4e3a Draw\uff08\uff09 \u7684\u65b9\u6cd5\uff0c\u5b83\u4e0d\u5e26\u4efb\u4f55\u53c2\u6570\uff0c\u4e5f\u4e0d\u8fd4\u56de\u4efb\u4f55\u5185\u5bb9\u3002\u5982\u679c\u4f60\u4ece\u6211\u8fd9\u91cc\u5f97\u5230\uff0c\u4f60\u5c31\u77e5\u9053\u7ec6\u8282\u4e86\u3002<\/p>\n<p>Given this, you are now obligated to override the Draw() method in the Circle class. If you do not, Circle is also assumed to be a non-creatable abstract type that must be adorned with the abstract keyword (which is obviously not useful in this example). Here is the code update:<br \/>\n\u9274\u4e8e\u6b64\uff0c\u60a8\u73b0\u5728\u6709\u4e49\u52a1\u91cd\u5199 Circle \u7c7b\u4e2d\u7684 Draw\uff08\uff09 \u65b9\u6cd5\u3002\u5982\u679c\u4f60\u4e0d\u8fd9\u6837\u505a\uff0cCircle \u4e5f\u88ab\u5047\u5b9a\u4e3a\u4e00\u4e2a\u4e0d\u53ef\u521b\u5efa\u7684\u62bd\u8c61\u7c7b\u578b\uff0c\u5fc5\u987b\u7528\u62bd\u8c61\u5173\u952e\u5b57\u6765\u88c5\u9970\uff08\u8fd9\u5728\u672c\u4f8b\u4e2d\u663e\u7136\u6ca1\u6709\u7528\uff09\u3002\u4ee5\u4e0b\u662f\u4ee3\u7801\u66f4\u65b0\uff1a<\/p>\n<p>\/\/ If we did not implement the abstract Draw() method, Circle would also be<br \/>\n\/\/ considered abstract, and would have to be marked abstract! class Circle : Shape<\/p>\n<p>{<br \/>\npublic Circle() {}<br \/>\npublic Circle(string name) : base(name) {} public override void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing {0} the Circle&quot;, PetName);<br \/>\n}<br \/>\n}<\/p>\n<p>The short answer is that you can now assume that anything deriving from Shape does indeed have a unique version of the Draw() method. To illustrate the full story of polymorphism, consider the following code:<br \/>\n\u7b80\u77ed\u7684\u56de\u7b54\u662f\uff0c\u60a8\u73b0\u5728\u53ef\u4ee5\u5047\u8bbe\u4ece Shape \u6d3e\u751f\u7684\u4efb\u4f55\u5185\u5bb9\u786e\u5b9e\u5177\u6709 Draw\uff08\uff09 \u65b9\u6cd5\u7684\u552f\u4e00\u7248\u672c\u3002\u4e3a\u4e86\u8bf4\u660e\u591a\u6001\u6027\u7684\u5b8c\u6574\u6545\u4e8b\uff0c\u8bf7\u8003\u8651\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<p>Console.WriteLine(&quot;<strong><strong><em> Fun with Polymorphism <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>\/\/ Make an array of Shape-compatible objects.<br \/>\nShape[] myShapes = {new Hexagon(), new Circle(), new Hexagon(&quot;Mick&quot;), new Circle(&quot;Beth&quot;), new Hexagon(&quot;Linda&quot;)};<\/p>\n<p>\/\/ Loop over each item and interact with the<br \/>\n\/\/ polymorphic interface. foreach (Shape s in myShapes)<br \/>\n{<br \/>\ns.Draw();<br \/>\n}<br \/>\nConsole.ReadLine();<\/p>\n<p>Here is the output from the modified code:<br \/>\n\u4ee5\u4e0b\u662f\u4fee\u6539\u540e\u7684\u4ee3\u7801\u7684\u8f93\u51fa\uff1a<\/p>\n<p><strong><strong><em> Fun with Polymorphism <\/em><\/strong><\/strong> Drawing NoName the Hexagon Drawing NoName the Circle<br \/>\nDrawing Mick the Hexagon Drawing Beth the Circle Drawing Linda the Hexagon<\/p>\n<p>This code illustrates polymorphism at its finest. Although it is not possible to directly create an instance of an abstract base class (the Shape), you are able to freely store references to any subclass with an abstract base variable. Therefore, when you are creating an array of Shapes, the array can hold any object deriving from the Shape base class (if you attempt to place Shape-incompatible objects into the array, you receive a compiler error).<br \/>\n\u6b64\u4ee3\u7801\u6700\u80fd\u8bf4\u660e\u591a\u6001\u6027\u3002\u867d\u7136\u4e0d\u80fd\u76f4\u63a5\u521b\u5efa\u62bd\u8c61\u57fa\u7c7b\uff08Shape\uff09\u7684\u5b9e\u4f8b\uff0c\u4f46\u60a8\u53ef\u4ee5\u4f7f\u7528\u62bd\u8c61\u57fa\u53d8\u91cf\u81ea\u7531\u5b58\u50a8\u5bf9\u4efb\u4f55\u5b50\u7c7b\u7684\u5f15\u7528\u3002\u56e0\u6b64\uff0c\u5728\u521b\u5efa Shape s \u6570\u7ec4\u65f6\uff0c\u8be5\u6570\u7ec4\u53ef\u4ee5\u4fdd\u5b58\u4ece Shape \u57fa\u7c7b\u6d3e\u751f\u7684\u4efb\u4f55\u5bf9\u8c61\uff08\u5982\u679c\u5c1d\u8bd5\u5c06\u5f62\u72b6\u4e0d\u517c\u5bb9\u7684\u5bf9\u8c61\u653e\u5165\u6570\u7ec4\u4e2d\uff0c\u5219\u4f1a\u6536\u5230\u7f16\u8bd1\u5668\u9519\u8bef\uff09\u3002<\/p>\n<p>Given that all items in the myShapes array do indeed derive from Shape, you know they all support the same \u201cpolymorphic interface\u201d (or said more plainly, they all have a Draw() method). As you iterate over the array of Shape references, it is at runtime that the underlying type is determined. At this point, the correct version of the Draw() method is invoked in memory.<br \/>\n\u9274\u4e8e myShapes \u6570\u7ec4\u4e2d\u7684\u6240\u6709\u9879\u786e\u5b9e\u90fd\u6d3e\u751f\u81ea Shape\uff0c\u60a8\u77e5\u9053\u5b83\u4eec\u90fd\u652f\u6301\u76f8\u540c\u7684\u201c\u591a\u6001\u63a5\u53e3\u201d\uff08\u6216\u8005\u66f4\u76f4\u767d\u5730\u8bf4\uff0c\u5b83\u4eec\u90fd\u6709\u4e00\u4e2a Draw\uff08\uff09 \u65b9\u6cd5\uff09\u3002\u5faa\u73af\u8bbf\u95ee Shape \u5f15\u7528\u6570\u7ec4\u65f6\uff0c\u5c06\u5728\u8fd0\u884c\u65f6\u786e\u5b9a\u57fa\u7840\u7c7b\u578b\u3002\u6b64\u65f6\uff0c\u5c06\u5728\u5185\u5b58\u4e2d\u8c03\u7528\u6b63\u786e\u7248\u672c\u7684 Draw\uff08\uff09 \u65b9\u6cd5\u3002<\/p>\n<p>This technique also makes it simple to safely extend the current hierarchy. For example, assume you derived more classes from the abstract Shape base class (Triangle, Square, etc.). Because of the<br \/>\npolymorphic interface, the code within your foreach loop would not have to change in the slightest, as the compiler enforces that only Shape-compatible types are placed within the myShapes array.<br \/>\n\u6b64\u6280\u672f\u8fd8\u4f7f\u5b89\u5168\u5730\u6269\u5c55\u5f53\u524d\u5c42\u6b21\u7ed3\u6784\u53d8\u5f97\u7b80\u5355\u3002\u4f8b\u5982\uff0c\u5047\u8bbe\u60a8\u4ece\u62bd\u8c61 Shape \u57fa\u7c7b\uff08\u4e09\u89d2\u5f62\u3001\u6b63\u65b9\u5f62\u7b49\uff09\u6d3e\u751f\u4e86\u66f4\u591a\u7c7b\u3002\u56e0\u4e3a\u591a\u6001\u63a5\u53e3\uff0cforeach \u5faa\u73af\u4e2d\u7684\u4ee3\u7801\u4e0d\u5fc5\u53d1\u751f\u4e1d\u6beb\u66f4\u6539\uff0c\u56e0\u4e3a\u7f16\u8bd1\u5668\u5f3a\u5236\u5c06\u4ec5\u4e0e Shape \u517c\u5bb9\u7684\u7c7b\u578b\u653e\u7f6e\u5728 myShapes \u6570\u7ec4\u4e2d\u3002<\/p>\n<h2>Understanding Member Shadowing<\/h2>\n<p>\u4e86\u89e3\u6210\u5458\u91cd\u5f71<\/p>\n<p>C# provides a facility that is the logical opposite of method overriding, termed shadowing. Formally speaking, if a derived class defines a member that is identical to a member defined in a base class, the derived class has shadowed the parent\u2019s version. In the real world, the possibility of this occurring is the greatest when you are subclassing from a class you (or your team) did not create yourself (such as when you purchase a third-party software package).<br \/>\nC# \u63d0\u4f9b\u4e86\u4e00\u79cd\u4e0e\u65b9\u6cd5\u91cd\u5199\uff08\u79f0\u4e3a\u91cd\u5f71\uff09\u5728\u903b\u8f91\u4e0a\u76f8\u53cd\u7684\u5de5\u5177\u3002\u4ece\u5f62\u5f0f\u4e0a\u8bb2\uff0c\u5982\u679c\u6d3e\u751f\u7c7b\u5b9a\u4e49\u7684\u6210\u5458\u4e0e\u57fa\u7c7b\u4e2d\u5b9a\u4e49\u7684\u6210\u5458\u76f8\u540c\uff0c\u5219\u6d3e\u751f\u7c7b\u5c06\u9690\u85cf\u7236\u7ea7\u7684\u7248\u672c\u3002\u5728\u73b0\u5b9e\u4e16\u754c\u4e2d\uff0c\u5f53\u60a8\u4ece\u60a8\uff08\u6216\u60a8\u7684\u56e2\u961f\uff09\u4e0d\u662f\u81ea\u5df1\u521b\u5efa\u7684\u7c7b\u8fdb\u884c\u5b50\u7c7b\u65f6\uff08\u4f8b\u5982\u5f53\u60a8\u8d2d\u4e70\u7b2c\u4e09\u65b9\u8f6f\u4ef6\u5305\u65f6\uff09\uff0c\u53d1\u751f\u8fd9\u79cd\u60c5\u51b5\u7684\u53ef\u80fd\u6027\u6700\u5927\u3002<\/p>\n<p>For the sake of illustration, assume you receive a class named ThreeDCircle from a co-worker (or classmate) that defines a subroutine named Draw() taking no arguments.<br \/>\n\u4e3a\u4e86\u4fbf\u4e8e\u8bf4\u660e\uff0c\u5047\u8bbe\u60a8\u4ece\u540c\u4e8b\uff08\u6216\u540c\u5b66\uff09\u90a3\u91cc\u6536\u5230\u4e00\u4e2a\u540d\u4e3a ThreeDCircle \u7684\u7c7b\uff0c\u8be5\u7c7b\u5b9a\u4e49\u4e86\u4e00\u4e2a\u540d\u4e3a Draw\uff08\uff09 \u7684\u5b50\u4f8b\u7a0b\uff0c\u4e0d\u5e26\u4efb\u4f55\u53c2\u6570\u3002<\/p>\n<p>namespace Shapes; class ThreeDCircle<br \/>\n{<br \/>\npublic void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing a 3D Circle&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>You figure that ThreeDCircle \u201cis-a\u201d Circle, so you derive from your existing Circle type.<br \/>\n\u60a8\u8ba4\u4e3a ThreeDCircle \u201c\u662f\u4e00\u4e2a\u201d\u5706\uff0c\u56e0\u6b64\u60a8\u4ece\u73b0\u6709\u7684 Circle \u7c7b\u578b\u6d3e\u751f\u3002<\/p>\n<p>class ThreeDCircle : Circle<br \/>\n{<br \/>\npublic void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing a 3D Circle&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>After you recompile, you find the following warning:<br \/>\n\u91cd\u65b0\u7f16\u8bd1\u540e\uff0c\u60a8\u4f1a\u770b\u5230\u4ee5\u4e0b\u8b66\u544a\uff1a<\/p>\n<p>'ThreeDCircle.Draw()' hides inherited member 'Circle.Draw()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.<\/p>\n<p>The problem is that you have a derived class (ThreeDCircle) that contains a method that is identical to an inherited method. To address this issue, you have a few options. You could simply update the child\u2019s version of Draw() using the override keyword (as suggested by the compiler). With this approach, the ThreeDCircle type is able to extend the parent\u2019s default behavior as required. However, if you don\u2019t have access to the code defining the base class (again, as would be the case in many third-party libraries), you would be unable to modify the Draw() method as a virtual member, as you don\u2019t have access to the code file!<br \/>\n\u95ee\u9898\u662f\u60a8\u6709\u4e00\u4e2a\u6d3e\u751f\u7c7b \uff08ThreeDCircle\uff09\uff0c\u5176\u4e2d\u5305\u542b\u4e0e\u7ee7\u627f\u65b9\u6cd5\u76f8\u540c\u7684\u65b9\u6cd5\u3002\u8981\u89e3\u51b3\u6b64\u95ee\u9898\uff0c\u60a8\u6709\u51e0\u79cd\u9009\u62e9\u3002\u60a8\u53ef\u4ee5\u7b80\u5355\u5730\u4f7f\u7528 override \u5173\u952e\u5b57\u66f4\u65b0\u5b50\u7248\u672c\u7684 Draw\uff08\uff09\uff08\u5982\u7f16\u8bd1\u5668\u5efa\u8bae\u7684\u90a3\u6837\uff09\u3002\u4f7f\u7528\u6b64\u65b9\u6cd5\uff0cThreeDCircle \u7c7b\u578b\u80fd\u591f\u6839\u636e\u9700\u8981\u6269\u5c55\u7236\u9879\u7684\u9ed8\u8ba4\u884c\u4e3a\u3002\u4f46\u662f\uff0c\u5982\u679c\u60a8\u65e0\u6743\u8bbf\u95ee\u5b9a\u4e49\u57fa\u7c7b\u7684\u4ee3\u7801\uff08\u540c\u6837\uff0c\u5c31\u50cf\u8bb8\u591a\u7b2c\u4e09\u65b9\u5e93\u4e2d\u7684\u60c5\u51b5\u4e00\u6837\uff09\uff0c\u5219\u5c06\u65e0\u6cd5\u5c06 Draw\uff08\uff09 \u65b9\u6cd5\u4fee\u6539\u4e3a\u865a\u62df\u6210\u5458\uff0c\u56e0\u4e3a\u60a8\u65e0\u6743\u8bbf\u95ee\u4ee3\u7801\u6587\u4ef6\uff01<\/p>\n<p>As an alternative, you can include the new keyword to the offending Draw() member of the derived type (ThreeDCircle, in this example). Doing so explicitly states that the derived type\u2019s implementation is intentionally designed to effectively ignore the parent\u2019s version (again, in the real world, this can be helpful if external software somehow conflicts with your current software).<br \/>\n\u4f5c\u4e3a\u66ff\u4ee3\u65b9\u6cd5\uff0c\u60a8\u53ef\u4ee5\u5c06 new \u5173\u952e\u5b57\u5305\u542b\u5728\u6d3e\u751f\u7c7b\u578b\uff08\u5728\u672c\u4f8b\u4e2d\u4e3a ThreeDCircle\uff09\u7684\u8fdd\u89c4 Draw\uff08\uff09 \u6210\u5458\u4e2d\u3002\u8fd9\u6837\u505a\u663e\u5f0f\u58f0\u660e\u6d3e\u751f\u7c7b\u578b\u7684\u5b9e\u73b0\u662f\u6709\u610f\u8bbe\u8ba1\u4e3a\u6709\u6548\u5730\u5ffd\u7565\u7236\u7248\u672c\uff08\u540c\u6837\uff0c\u5728\u73b0\u5b9e\u4e16\u754c\u4e2d\uff0c\u5982\u679c\u5916\u90e8\u8f6f\u4ef6\u4e0e\u60a8\u5f53\u524d\u7684\u8f6f\u4ef6\u53d1\u751f\u51b2\u7a81\uff0c\u8fd9\u53ef\u80fd\u4f1a\u5f88\u6709\u5e2e\u52a9\uff09\u3002<\/p>\n<p>\/\/ This class extends Circle and hides the inherited Draw() method. class ThreeDCircle : Circle<br \/>\n{<br \/>\n\/\/ Hide any Draw() implementation above me. public new void Draw()<\/p>\n<p>{<br \/>\nConsole.WriteLine(&quot;Drawing a 3D Circle&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>You can also apply the new keyword to any member type inherited from a base class (field, constant, static member, or property). As a further example, assume that ThreeDCircle wants to hide the inherited PetName property.<br \/>\n\u8fd8\u53ef\u4ee5\u5c06 new \u5173\u952e\u5b57\u5e94\u7528\u4e8e\u4ece\u57fa\u7c7b\u7ee7\u627f\u7684\u4efb\u4f55\u6210\u5458\u7c7b\u578b\uff08\u5b57\u6bb5\u3001\u5e38\u91cf\u3001\u9759\u6001\u6210\u5458\u6216\u5c5e\u6027\uff09\u3002\u4f5c\u4e3a\u8fdb\u4e00\u6b65\u7684\u793a\u4f8b\uff0c\u5047\u8bbe ThreeDCircle \u60f3\u8981\u9690\u85cf\u7ee7\u627f\u7684 PetName \u5c5e\u6027\u3002<\/p>\n<p>class ThreeDCircle : Circle<br \/>\n{<br \/>\n\/\/ Hide the PetName property above me. public new string PetName { get; set; }<\/p>\n<p>\/\/ Hide any Draw() implementation above me. public new void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing a 3D Circle&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Finally, be aware that it is still possible to trigger the base class implementation of a shadowed member using an explicit cast, as described in the next section. The following code shows an example:<br \/>\n\u6700\u540e\uff0c\u8bf7\u6ce8\u610f\uff0c\u4ecd\u7136\u53ef\u4ee5\u4f7f\u7528\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u89e6\u53d1\u91cd\u5f71\u6210\u5458\u7684\u57fa\u7c7b\u5b9e\u73b0\uff0c\u5982\u4e0b\u4e00\u8282\u6240\u8ff0\u3002\u4ee5\u4e0b\u4ee3\u7801\u663e\u793a\u4e86\u4e00\u4e2a\u793a\u4f8b\uff1a<\/p>\n<p>...<br \/>\n\/\/ This calls the Draw() method of the ThreeDCircle. ThreeDCircle o = new ThreeDCircle();<br \/>\no.Draw();<\/p>\n<p>\/\/ This calls the Draw() method of the parent! ((Circle)o).Draw();<br \/>\nConsole.ReadLine();<\/p>\n<h2>Understanding Base Class\/Derived Class Casting Rules<\/h2>\n<p>\u4e86\u89e3\u57fa\u7c7b\/\u6d3e\u751f\u7c7b\u8f6c\u6362\u89c4\u5219<\/p>\n<p>Now that you can build a family of related class types, you need to learn the rules of class casting operations. To do so, let\u2019s return to the employee hierarchy created earlier in this chapter and add some new methods to the Program.cs file (if you are following along, open the Employees project). As described later in this chapter, the ultimate base class in the system is System.Object. Therefore, everything \u201cis-an\u201d Object and can be treated as such. Given this fact, it is legal to store an instance of any type within an object variable.<br \/>\n\u73b0\u5728\uff0c\u60a8\u53ef\u4ee5\u6784\u5efa\u4e00\u7cfb\u5217\u76f8\u5173\u7684\u7c7b\u7c7b\u578b\uff0c\u60a8\u9700\u8981\u5b66\u4e60\u7c7b\u8f6c\u6362\u64cd\u4f5c\u7684\u89c4\u5219\u3002\u4e3a\u6b64\uff0c\u8ba9\u6211\u4eec\u8fd4\u56de\u5230\u672c\u7ae0\u524d\u9762\u521b\u5efa\u7684\u5458\u5de5\u5c42\u6b21\u7ed3\u6784\uff0c\u5e76\u5411 Program.cs \u6587\u4ef6\u6dfb\u52a0\u4e00\u4e9b\u65b0\u65b9\u6cd5\uff08\u5982\u679c\u60a8\u6b63\u5728\u7ee7\u7eed\u64cd\u4f5c\uff0c\u8bf7\u6253\u5f00 Employees \u9879\u76ee\uff09\u3002\u5982\u672c\u7ae0\u540e\u9762\u6240\u8ff0\uff0c\u7cfb\u7edf\u4e2d\u7684\u6700\u7ec8\u57fa\u7c7b\u662f System.Object\u3002\u56e0\u6b64\uff0c\u4e00\u5207\u90fd\u201c\u662f\u201d\u5bf9\u8c61\uff0c\u53ef\u4ee5\u8fd9\u6837\u5bf9\u5f85\u3002\u9274\u4e8e\u8fd9\u4e00\u4e8b\u5b9e\uff0c\u5728\u5bf9\u8c61\u53d8\u91cf\u4e2d\u5b58\u50a8\u4efb\u4f55\u7c7b\u578b\u7684\u5b9e\u4f8b\u90fd\u662f\u5408\u6cd5\u7684\u3002<\/p>\n<p>static void CastingExamples()<br \/>\n{<br \/>\n\/\/ A Manager &quot;is-a&quot; System.Object, so we can<br \/>\n\/\/ store a Manager reference in an object variable just fine.<br \/>\nobject frank = new Manager(&quot;Frank Zappa&quot;, 9, 3000, 40000, &quot;111-11-1111&quot;, 5);<br \/>\n}<\/p>\n<p>In the Employees project, Managers, SalesPerson, and PtSalesPerson types all extend Employee, so you can store any of these objects in a valid base class reference. Therefore, the following statements are also legal:<br \/>\n\u5728\u201c\u5458\u5de5\u201d\u9879\u76ee\u4e2d\uff0c\u201c\u7ecf\u7406\u201d\u3001\u201c\u9500\u552e\u4eba\u5458\u201d\u548c\u201cPtSales\u4eba\u5458\u201d\u7c7b\u578b\u90fd\u6269\u5c55\u4e86\u201c\u5458\u5de5\u201d\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u5c06\u8fd9\u4e9b\u5bf9\u8c61\u4e2d\u7684\u4efb\u4f55\u4e00\u4e2a\u5b58\u50a8\u5728\u6709\u6548\u7684\u57fa\u7c7b\u5f15\u7528\u4e2d\u3002\u56e0\u6b64\uff0c\u4ee5\u4e0b\u58f0\u660e\u4e5f\u662f\u5408\u6cd5\u7684\uff1a<\/p>\n<p>static void CastingExamples()<br \/>\n{<br \/>\n\/\/ A Manager &quot;is-a&quot; System.Object, so we can<br \/>\n\/\/ store a Manager reference in an object variable just fine.<br \/>\nobject frank = new Manager(&quot;Frank Zappa&quot;, 9, 3000, 40000, &quot;111-11-1111&quot;, 5);<\/p>\n<p>\/\/ A Manager &quot;is-an&quot; Employee too.<br \/>\nEmployee moonUnit = new Manager(&quot;MoonUnit Zappa&quot;, 2, 3001, 20000, &quot;101-11-1321&quot;, 1);<\/p>\n<p>\/\/ A PtSalesPerson &quot;is-a&quot; SalesPerson.<br \/>\nSalesPerson jill = new PtSalesPerson(&quot;Jill&quot;, 834, 3002, 100000, &quot;111-12-1119&quot;, 90);<br \/>\n}<\/p>\n<p>The first law of casting between class types is that when two classes are related by an \u201cis-a\u201d relationship, it is always safe to store a derived object within a base class reference. Formally, this is called an implicit cast, as \u201cit just works\u201d given the laws of inheritance. This leads to some powerful programming constructs. For example, assume you have defined a new method within your current Program.cs file.<br \/>\n\u5728\u7c7b\u7c7b\u578b\u4e4b\u95f4\u8fdb\u884c\u5f3a\u5236\u8f6c\u6362\u7684\u7b2c\u4e00\u5b9a\u5f8b\u662f\uff0c\u5f53\u4e24\u4e2a\u7c7b\u901a\u8fc7\u201cis-a\u201d\u5173\u7cfb\u5173\u8054\u65f6\uff0c\u5c06\u6d3e\u751f\u5bf9\u8c61\u5b58\u50a8\u5728\u57fa\u7c7b\u5f15\u7528\u4e2d\u59cb\u7ec8\u662f\u5b89\u5168\u7684\u3002\u4ece\u5f62\u5f0f\u4e0a\u8bb2\uff0c\u8fd9\u88ab\u79f0\u4e3a\u9690\u5f0f\u5f3a\u5236\u8f6c\u6362\uff0c\u56e0\u4e3a\u8003\u8651\u5230\u7ee7\u627f\u6cd5\u5219\uff0c\u201c\u5b83\u53ea\u662f\u6709\u6548\u201d\u3002\u8fd9\u5bfc\u81f4\u4e86\u4e00\u4e9b\u5f3a\u5927\u7684\u7f16\u7a0b\u7ed3\u6784\u3002\u4f8b\u5982\uff0c\u5047\u8bbe\u60a8\u5df2\u5728\u5f53\u524d Program.cs \u6587\u4ef6\u4e2d\u5b9a\u4e49\u4e86\u4e00\u4e2a\u65b0\u65b9\u6cd5\u3002<\/p>\n<p>static void GivePromotion(Employee emp)<br \/>\n{<br \/>\n\/\/ Increase pay...<br \/>\n\/\/ Give new parking space in company garage...<\/p>\n<p>Console.WriteLine(&quot;{0} was promoted!&quot;, emp.Name);<br \/>\n}<\/p>\n<p>Because this method takes a single parameter of type Employee, you can effectively pass any descendant from the Employee class into this method directly, given the \u201cis-a\u201d relationship.<br \/>\n\u7531\u4e8e\u6b64\u65b9\u6cd5\u91c7\u7528 Employee \u7c7b\u578b\u7684\u5355\u4e2a\u53c2\u6570\uff0c\u56e0\u6b64\u5728\u7ed9\u5b9a\u201cis-a\u201d\u5173\u7cfb\u7684\u60c5\u51b5\u4e0b\uff0c\u53ef\u4ee5\u6709\u6548\u5730\u5c06 Employee \u7c7b\u4e2d\u7684\u4efb\u4f55\u540e\u4ee3\u76f4\u63a5\u4f20\u9012\u5230\u6b64\u65b9\u6cd5\u4e2d\u3002<\/p>\n<p>static void CastingExamples()<br \/>\n{<br \/>\n\/\/ A Manager &quot;is-a&quot; System.Object, so we can<br \/>\n\/\/ store a Manager reference in an object variable just fine.<br \/>\nobject frank = new Manager(&quot;Frank Zappa&quot;, 9, 3000, 40000, &quot;111-11-1111&quot;, 5);<\/p>\n<p>\/\/ A Manager &quot;is-an&quot; Employee too.<br \/>\nEmployee moonUnit = new Manager(&quot;MoonUnit Zappa&quot;, 2, 3001, 20000, &quot;101-11-1321&quot;, 1); GivePromotion(moonUnit);<\/p>\n<p>\/\/ A PTSalesPerson &quot;is-a&quot; SalesPerson.<br \/>\nSalesPerson jill = new PtSalesPerson(&quot;Jill&quot;, 834, 3002, 100000, &quot;111-12-1119&quot;, 90); GivePromotion(jill);<br \/>\n}<\/p>\n<p>The previous code compiles given the implicit cast from the base class type (Employee) to the derived type. However, what if you also wanted to promote Frank Zappa (currently stored in a general System.<br \/>\nObject reference)? If you pass the frank object directly into this method, you will find a compiler error as follows:<br \/>\n\u524d\u9762\u7684\u4ee3\u7801\u5728\u7ed9\u5b9a\u4ece\u57fa\u7c7b\u7c7b\u578b \uff08Employee\uff09 \u5230\u6d3e\u751f\u7c7b\u578b\u7684\u9690\u5f0f\u5f3a\u5236\u8f6c\u6362\u7684\u60c5\u51b5\u4e0b\u8fdb\u884c\u7f16\u8bd1\u3002\u4f46\u662f\uff0c\u5982\u679c\u60a8\u8fd8\u60f3\u63a8\u5e7f\u5f17\u5170\u514b\u624e\u5e15\uff08\u76ee\u524d\u5b58\u50a8\u5728\u901a\u7528\u7cfb\u7edf\u4e2d\u3002\u5bf9\u8c61\u5f15\u7528\uff09\uff1f\u5982\u679c\u5c06 frank \u5bf9\u8c61\u76f4\u63a5\u4f20\u9012\u5230\u6b64\u65b9\u6cd5\u4e2d\uff0c\u5219\u4f1a\u53d1\u73b0\u7f16\u8bd1\u5668\u9519\u8bef\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>object frank = new Manager(&quot;Frank Zappa&quot;, 9, 3000, 40000, &quot;111-11-1111&quot;, 5);<br \/>\n\/\/ Error!<br \/>\nGivePromotion(frank);<\/p>\n<p>The problem is that you are attempting to pass in a variable that is not declared as an Employee but a more general System.Object. Given that object is higher up the inheritance chain than Employee, the compiler will not allow for an implicit cast, in an effort to keep your code as type-safe as possible.<br \/>\n\u95ee\u9898\u662f\u60a8\u6b63\u5728\u5c1d\u8bd5\u4f20\u5165\u4e00\u4e2a\u672a\u58f0\u660e\u4e3a Employee \u800c\u662f\u66f4\u901a\u7528\u7684 System.Object \u7684\u53d8\u91cf\u3002\u5047\u8bbe\u8be5\u5bf9\u8c61\u5728\u7ee7\u627f\u94fe\u4e2d\u9ad8\u4e8e Employee\uff0c\u7f16\u8bd1\u5668\u5c06\u4e0d\u5141\u8bb8\u9690\u5f0f\u5f3a\u5236\u8f6c\u6362\uff0c\u4ee5\u4f7f\u4ee3\u7801\u5c3d\u53ef\u80fd\u7c7b\u578b\u5b89\u5168\u3002<\/p>\n<p>Even though you can figure out that the object reference is pointing to an Employee-compatible class in memory, the compiler cannot, as that will not be known until runtime. You can satisfy the compiler by performing an explicit cast. This is the second law of casting: you can, in such cases, explicitly downcast using the C# casting operator. The basic template to follow when performing an explicit cast looks something like the following:<br \/>\n\u5373\u4f7f\u60a8\u53ef\u4ee5\u786e\u5b9a\u5bf9\u8c61\u5f15\u7528\u6307\u5411\u5185\u5b58\u4e2d\u4e0e Employee \u517c\u5bb9\u7684\u7c7b\uff0c\u7f16\u8bd1\u5668\u4e5f\u4e0d\u80fd\uff0c\u56e0\u4e3a\u76f4\u5230\u8fd0\u884c\u65f6\u624d\u4f1a\u77e5\u9053\u3002\u53ef\u4ee5\u901a\u8fc7\u6267\u884c\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u6765\u6ee1\u8db3\u7f16\u8bd1\u5668\u7684\u8981\u6c42\u3002\u8fd9\u662f\u5f3a\u5236\u8f6c\u6362\u7684\u7b2c\u4e8c\u5b9a\u5f8b\uff1a\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u53ef\u4ee5\u4f7f\u7528 C# \u5f3a\u5236\u8f6c\u6362\u8fd0\u7b97\u7b26\u663e\u5f0f\u5411\u4e0b\u8f6c\u6362\u3002\u6267\u884c\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u65f6\u8981\u9075\u5faa\u7684\u57fa\u672c\u6a21\u677f\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>(ClassIWantToCastTo)referenceIHave<\/p>\n<p>Thus, to pass the object variable into the GivePromotion() method, you can author the following code:<br \/>\n\u56e0\u6b64\uff0c\u8981\u5c06\u5bf9\u8c61\u53d8\u91cf\u4f20\u9012\u5230 GivePromotion\uff08\uff09 \u65b9\u6cd5\u4e2d\uff0c\u60a8\u53ef\u4ee5\u7f16\u5199\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<p>\/\/ OK! GivePromotion((Manager)frank);<\/p>\n<h2>Using the C# as Keyword<\/h2>\n<p>\u4f7f\u7528 C# \u4f5c\u4e3a\u5173\u952e\u5b57<\/p>\n<p>Be aware that explicit casting is evaluated at runtime, not compile time. For the sake of argument, assume your Employees project had a copy of the Hexagon class created earlier in this chapter. For simplicity, you can add the following class to the current project:<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u662f\u5728\u8fd0\u884c\u65f6\u8ba1\u7b97\u7684\uff0c\u800c\u4e0d\u662f\u5728\u7f16\u8bd1\u65f6\u8ba1\u7b97\u7684\u3002\u4e3a\u4e86\u4fbf\u4e8e\u8ba8\u8bba\uff0c\u5047\u8bbe\u60a8\u7684 Employees \u9879\u76ee\u5177\u6709\u672c\u7ae0\u524d\u9762\u521b\u5efa\u7684 Hexagon \u7c7b\u7684\u526f\u672c\u3002\u4e3a\u7b80\u5355\u8d77\u89c1\uff0c\u53ef\u4ee5\u5c06\u4ee5\u4e0b\u7c7b\u6dfb\u52a0\u5230\u5f53\u524d\u9879\u76ee\u4e2d\uff1a<\/p>\n<p>namespace Shapes; class Hexagon<br \/>\n{<br \/>\npublic void Draw()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Drawing a hexagon!&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Although casting the Employee object to a shape object makes absolutely no sense, code such as the following could compile without error:<br \/>\n\u5c3d\u7ba1\u5c06 Employee \u5bf9\u8c61\u5f3a\u5236\u8f6c\u6362\u4e3a\u5f62\u72b6\u5bf9\u8c61\u7edd\u5bf9\u6ca1\u6709\u610f\u4e49\uff0c\u4f46\u5982\u4e0b\u4ee3\u7801\u53ef\u4ee5\u7f16\u8bd1\u800c\u4e0d\u4f1a\u51fa\u9519\uff1a<br \/>\n\/\/ Ack! You can't cast frank to a Hexagon, but this compiles fine! object frank = new Manager();<br \/>\nHexagon hex = (Hexagon)frank;<\/p>\n<p>However, you would receive a runtime error, or, more formally, a runtime exception. Chapter 7 will examine the full details of structured exception handling; however, it is worth pointing out, for the time being, that when you are performing an explicit cast, you can trap the possibility of an invalid cast using the try and catch keywords (again, see Chapter 7 for full details).<br \/>\n\u4f46\u662f\uff0c\u60a8\u5c06\u6536\u5230\u8fd0\u884c\u65f6\u9519\u8bef\uff0c\u6216\u8005\u66f4\u6b63\u5f0f\u5730\u8bf4\uff0c\u4f1a\u6536\u5230\u8fd0\u884c\u65f6\u5f02\u5e38\u3002\u7b2c7\u7ae0\u5c06\u7814\u7a76\u7ed3\u6784\u5316\u5f02\u5e38\u5904\u7406\u7684\u5168\u90e8\u7ec6\u8282;\u4f46\u662f\uff0c\u503c\u5f97\u6307\u51fa\u7684\u662f\uff0c\u6682\u65f6\uff0c\u5f53\u60a8\u6267\u884c\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u65f6\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528 try \u548c catch \u5173\u952e\u5b57\u6355\u83b7\u65e0\u6548\u8f6c\u6362\u7684\u53ef\u80fd\u6027\uff08\u540c\u6837\uff0c\u6709\u5173\u5b8c\u6574\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605\u7b2c 7 \u7ae0\uff09\u3002<\/p>\n<p>\/\/ Catch a possible invalid cast. object frank = new Manager(); Hexagon hex;<br \/>\ntry<br \/>\n{<br \/>\nhex = (Hexagon)frank;<br \/>\n}<br \/>\ncatch (InvalidCastException ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<\/p>\n<p>Obviously, this is a contrived example; you would never bother casting between these types in this situation. However, assume you have an array of System.Object types, only a few of which contain Employee-compatible objects. In this case, you would like to determine whether an item in an array is compatible to begin with and, if so, perform the cast.<br \/>\n\u663e\u7136\uff0c\u8fd9\u662f\u4e00\u4e2a\u4eba\u4e3a\u7684\u4f8b\u5b50;\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u6c38\u8fdc\u4e0d\u4f1a\u5728\u8fd9\u4e9b\u7c7b\u578b\u4e4b\u95f4\u8fdb\u884c\u8f6c\u6362\u3002\u4f46\u662f\uff0c\u5047\u8bbe\u60a8\u6709\u4e00\u4e2a System.Object \u7c7b\u578b\u7684\u6570\u7ec4\uff0c\u5176\u4e2d\u53ea\u6709\u5c11\u6570\u5305\u542b\u4e0e\u5458\u5de5\u517c\u5bb9\u7684\u5bf9\u8c61\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u5e0c\u671b\u786e\u5b9a\u6570\u7ec4\u4e2d\u7684\u9879\u662f\u5426\u517c\u5bb9\uff0c\u5982\u679c\u662f\uff0c\u5219\u6267\u884c\u5f3a\u5236\u8f6c\u6362\u3002<\/p>\n<p>C# provides the as keyword to quickly determine at runtime whether a given type is compatible with another. When you use the as keyword, you are able to determine compatibility by checking against a null return value. Consider the following:<br \/>\nC# \u63d0\u4f9b as \u5173\u952e\u5b57\uff0c\u7528\u4e8e\u5728\u8fd0\u884c\u65f6\u5feb\u901f\u786e\u5b9a\u7ed9\u5b9a\u7c7b\u578b\u662f\u5426\u4e0e\u53e6\u4e00\u4e2a\u7c7b\u578b\u517c\u5bb9\u3002\u4f7f\u7528 as \u5173\u952e\u5b57\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7\u68c0\u67e5 null \u8fd4\u56de\u503c\u6765\u786e\u5b9a\u517c\u5bb9\u6027\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b\u4e8b\u9879\uff1a<\/p>\n<p>\/\/ Use &quot;as&quot; to test compatibility. object[] things = new object[4]; things[0] = new Hexagon(); things[1] = false;<br \/>\nthings[2] = new Manager(); things[3] = &quot;Last thing&quot;;<\/p>\n<p>foreach (object item in things)<br \/>\n{<br \/>\nHexagon h = item as Hexagon; if (h == null)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Item is not a hexagon&quot;);<br \/>\n}<br \/>\nelse<br \/>\n{<br \/>\nh.Draw();<br \/>\n}<br \/>\n}<\/p>\n<p>Here, you loop over each item in the array of objects, checking each one for compatibility with the Hexagon class. If (and only if!) you find a Hexagon-compatible object, you invoke the Draw() method. Otherwise, you simply report the items are not compatible.<br \/>\n\u5728\u8fd9\u91cc\uff0c\u60a8\u904d\u5386\u5bf9\u8c61\u6570\u7ec4\u4e2d\u7684\u6bcf\u4e2a\u9879\u76ee\uff0c\u68c0\u67e5\u6bcf\u4e2a\u9879\u76ee\u662f\u5426\u4e0e Hexagon \u7c7b\u517c\u5bb9\u3002\u5982\u679c\uff08\u5e76\u4e14\u4ec5\u5f53\uff01\uff09\u627e\u5230\u4e00\u4e2a\u4e0e Hexagon \u517c\u5bb9\u7684\u5bf9\u8c61\uff0c\u5219\u8c03\u7528 Draw\uff08\uff09 \u65b9\u6cd5\u3002\u5426\u5219\uff0c\u60a8\u53ea\u9700\u62a5\u544a\u9879\u76ee\u4e0d\u517c\u5bb9\u3002<\/p>\n<h2>Using the C# is Keyword (Updated 7.0, 9.0)<\/h2>\n<p>\u4f7f\u7528 C# is \u5173\u952e\u5b57\uff08\u66f4\u65b0 7.0\u30019.0\uff09<\/p>\n<p>In addition to the as keyword, the C# language provides the is keyword to determine whether two items are compatible. Unlike the as keyword, however, the is keyword returns false, rather than a null reference, if the types are incompatible. Currently, the GivePromotion() method has been designed to take any possible type derived from Employee. Consider the following update, which now checks to see exactly which \u201ctype of employee\u201d has been passed in:<br \/>\n\u9664\u4e86 as \u5173\u952e\u5b57\u4e4b\u5916\uff0cC# \u8bed\u8a00\u8fd8\u63d0\u4f9b\u4e86 is \u5173\u952e\u5b57\u6765\u786e\u5b9a\u4e24\u4e2a\u9879\u662f\u5426\u517c\u5bb9\u3002\u4f46\u662f\uff0c\u4e0e as \u5173\u952e\u5b57\u4e0d\u540c\uff0c\u5982\u679c\u7c7b\u578b\u4e0d\u517c\u5bb9\uff0c\u5219 is \u5173\u952e\u5b57\u8fd4\u56de false\uff0c\u800c\u4e0d\u662f null \u5f15\u7528\u3002\u76ee\u524d\uff0cGivePromotion\uff08\uff09 \u65b9\u6cd5\u5df2\u88ab\u8bbe\u8ba1\u4e3a\u91c7\u7528\u4ece Employee \u6d3e\u751f\u7684\u4efb\u4f55\u53ef\u80fd\u7c7b\u578b\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b\u66f4\u65b0\uff0c\u8be5\u66f4\u65b0\u73b0\u5728\u68c0\u67e5\u662f\u5426\u51c6\u786e\u67e5\u770b\u4f20\u5165\u7684\u201c\u5458\u5de5\u7c7b\u578b\u201d\uff1a<\/p>\n<p>static void GivePromotion(Employee emp)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} was promoted!&quot;, emp.Name); if (emp is SalesPerson)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} made {1} sale(s)!&quot;, emp.Name, ((SalesPerson)emp).SalesNumber); Console.WriteLine();<br \/>\n}<br \/>\nelse if (emp is Manager)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} had {1} stock options...&quot;, emp.Name, ((Manager)emp).StockOptions); Console.WriteLine();<br \/>\n}<br \/>\n}<\/p>\n<p>Here, you are performing a runtime check to determine what the incoming base class reference is actually pointing to in memory. After you determine whether you received a SalesPerson or Manager type, you are able to perform an explicit cast to gain access to the specialized members of the class. Also notice that you are not required to wrap your casting operations within a try\/catch construct, as you know that the cast is safe if you enter either if scope, given your conditional check.<br \/>\n\u5728\u8fd9\u91cc\uff0c\u60a8\u5c06\u6267\u884c\u8fd0\u884c\u65f6\u68c0\u67e5\uff0c\u4ee5\u786e\u5b9a\u4f20\u5165\u57fa\u7c7b\u5f15\u7528\u5728\u5185\u5b58\u4e2d\u5b9e\u9645\u6307\u5411\u7684\u5185\u5bb9\u3002\u786e\u5b9a\u662f\u5426\u6536\u5230\u201c\u9500\u552e\u4eba\u5458\u201d\u6216\u201c\u7ecf\u7406\u201d\u7c7b\u578b\u540e\uff0c\u53ef\u4ee5\u6267\u884c\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u4ee5\u83b7\u53d6\u5bf9\u7c7b\u4e2d\u4e13\u7528\u6210\u5458\u7684\u8bbf\u95ee\u6743\u9650\u3002\u53e6\u8bf7\u6ce8\u610f\uff0c\u60a8\u4e0d\u9700\u8981\u5c06\u8f6c\u6362\u64cd\u4f5c\u5305\u88c5\u5728 try\/catch \u6784\u9020\u4e2d\uff0c\u56e0\u4e3a\u60a8\u77e5\u9053\uff0c\u5982\u679c\u8f93\u5165 if \u8303\u56f4\uff0c\u7ed9\u5b9a\u6761\u4ef6\u68c0\u67e5\uff0c\u5219\u8f6c\u6362\u662f\u5b89\u5168\u7684\u3002<\/p>\n<p>New in C# 7.0, the is keyword can also assign the converted type to a variable if the cast works. This cleans up the preceding method by preventing the \u201cdouble-cast\u201d problem. In the preceding example, the first cast is done when checking to see whether the type matches, and if it does, then the variable has to be cast again. Consider this update to the preceding method:<br \/>\n\u5728\u8fd9\u91cc\uff0c\u60a8\u5c06\u6267\u884c\u8fd0\u884c\u65f6\u68c0\u67e5\uff0c\u4ee5\u786e\u5b9a\u4f20\u5165\u57fa\u7c7b\u5f15\u7528\u5728\u5185\u5b58\u4e2d\u5b9e\u9645\u6307\u5411\u7684\u5185\u5bb9\u3002\u786e\u5b9a\u662f\u5426\u6536\u5230\u201c\u9500\u552e\u4eba\u5458\u201d\u6216\u201c\u7ecf\u7406\u201d\u7c7b\u578b\u540e\uff0c\u53ef\u4ee5\u6267\u884c\u663e\u5f0f\u5f3a\u5236\u8f6c\u6362\u4ee5\u83b7\u53d6\u5bf9\u7c7b\u4e2d\u4e13\u7528\u6210\u5458\u7684\u8bbf\u95ee\u6743\u9650\u3002\u53e6\u8bf7\u6ce8\u610f\uff0c\u60a8\u4e0d\u9700\u8981\u5c06\u8f6c\u6362\u64cd\u4f5c\u5305\u88c5\u5728 try\/catch \u6784\u9020\u4e2d\uff0c\u56e0\u4e3a\u60a8\u77e5\u9053\uff0c\u5982\u679c\u8f93\u5165 if \u8303\u56f4\uff0c\u7ed9\u5b9a\u6761\u4ef6\u68c0\u67e5\uff0c\u5219\u8f6c\u6362\u662f\u5b89\u5168\u7684\u3002<\/p>\n<p>static void GivePromotion(Employee emp)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} was promoted!&quot;, emp.Name);<br \/>\n\/\/Check if is SalesPerson, assign to variable s if (emp is SalesPerson s)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} made {1} sale(s)!&quot;, s.Name, s.SalesNumber); Console.WriteLine();<br \/>\n}<br \/>\n\/\/Check if is Manager, if it is, assign to variable m else if (emp is Manager m)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} had {1} stock options...&quot;, m.Name, m.StockOptions); Console.WriteLine();<br \/>\n}<br \/>\n}<\/p>\n<p>C# 9.0 introduced additional pattern matching capabilities (covered in Chapter 3). These updated pattern matches can be used with the is keyword. For example, to check if the employee is not a Manager and not a SalesPerson, use the following code:<br \/>\nC# 9.0 \u5f15\u5165\u4e86\u5176\u4ed6\u6a21\u5f0f\u5339\u914d\u529f\u80fd\uff08\u5728\u7b2c 3 \u7ae0\u4e2d\u4ecb\u7ecd\uff09\u3002\u8fd9\u4e9b\u66f4\u65b0\u7684\u6a21\u5f0f\u5339\u914d\u53ef\u4ee5\u4e0e is \u5173\u952e\u5b57\u4e00\u8d77\u4f7f\u7528\u3002\u4f8b\u5982\uff0c\u82e5\u8981\u68c0\u67e5\u5458\u5de5\u662f\u5426\u4e0d\u662f\u7ecf\u7406\uff0c\u4e5f\u4e0d\u662f\u9500\u552e\u4eba\u5458\uff0c\u8bf7\u4f7f\u7528\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<p>if (emp is not Manager and not SalesPerson)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Unable to promote {0}. Wrong employee type&quot;, emp.Name); Console.WriteLine();<br \/>\n}<\/p>\n<h2>Discards with the is Keyword (New 7.0)<\/h2>\n<p>\u4f7f\u7528 is \u5173\u952e\u5b57\u4e22\u5f03\uff08\u65b0\u7248 7.0\uff09<\/p>\n<p>The is keyword can also be used in conjunction with the discard variable placeholder. If you want to create a catchall in your if or switch statement, you can do so as follows:<br \/>\nis \u5173\u952e\u5b57\u4e5f\u53ef\u4ee5\u4e0e\u4e22\u5f03\u53d8\u91cf\u5360\u4f4d\u7b26\u7ed3\u5408\u4f7f\u7528\u3002\u5982\u679c\u8981\u5728 if \u6216 switch \u8bed\u53e5\u4e2d\u521b\u5efa\u5305\u7f57\u4e07\u8c61\uff0c\u53ef\u4ee5\u6309\u5982\u4e0b\u65b9\u5f0f\u6267\u884c\u6b64\u64cd\u4f5c\uff1a<\/p>\n<p>if (obj is var _)<br \/>\n{<br \/>\n\/\/do something<br \/>\n}<\/p>\n<p>This will match everything, so be careful about the order in which you use the comparer with the discard. The updated GivePromotion() method is shown here:<br \/>\nis \u5173\u952e\u5b57\u4e5f\u53ef\u4ee5\u4e0e\u4e22\u5f03\u53d8\u91cf\u5360\u4f4d\u7b26\u7ed3\u5408\u4f7f\u7528\u3002\u5982\u679c\u8981\u5728 if \u6216 switch \u8bed\u53e5\u4e2d\u521b\u5efa\u5305\u7f57\u4e07\u8c61\uff0c\u53ef\u4ee5\u6309\u5982\u4e0b\u65b9\u5f0f\u6267\u884c\u6b64\u64cd\u4f5c\uff1a<\/p>\n<p>if (emp is SalesPerson s)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} made {1} sale(s)!&quot;, s.Name, s.SalesNumber); Console.WriteLine();<br \/>\n}<br \/>\n\/\/Check if is Manager, if it is, assign to variable m else if (emp is Manager m)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} had {1} stock options...&quot;, m.Name, m.StockOptions); Console.WriteLine();<br \/>\n}<br \/>\nelse if (emp is var _)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Unable to promote {0}. Wrong employee type&quot;, emp.Name); Console.WriteLine();<br \/>\n}<\/p>\n<p>The final if statement will catch any Employee instance that is not a Manager, SalesPerson, or PtSalesPerson. Remember that you can downcast to a base class, so the PtSalesPerson will register as a SalesPerson.<br \/>\nis \u5173\u952e\u5b57\u4e5f\u53ef\u4ee5\u4e0e\u4e22\u5f03\u53d8\u91cf\u5360\u4f4d\u7b26\u7ed3\u5408\u4f7f\u7528\u3002\u5982\u679c\u8981\u5728 if \u6216 switch \u8bed\u53e5\u4e2d\u521b\u5efa\u5305\u7f57\u4e07\u8c61\uff0c\u53ef\u4ee5\u6309\u5982\u4e0b\u65b9\u5f0f\u6267\u884c\u6b64\u64cd\u4f5c\uff1a<\/p>\n<h2>Revisiting Pattern Matching (New 7.0)<\/h2>\n<p>\u91cd\u65b0\u5ba1\u89c6\u6a21\u5f0f\u5339\u914d\uff08\u65b0 7.0\uff09<\/p>\n<p>Chapter 3 introduced the C# 7 feature of pattern matching along with the updates that came with C# 9.0. Now that you have a firm understanding of casting, it\u2019s time for a better example. The preceding example can now be cleanly updated to use a pattern matching switch statement, as follows:<br \/>\n\u7b2c 3 \u7ae0\u4ecb\u7ecd\u4e86\u6a21\u5f0f\u5339\u914d\u7684 C# 7 \u529f\u80fd\u4ee5\u53ca C# 9.0 \u9644\u5e26\u7684\u66f4\u65b0\u3002\u65e2\u7136\u60a8\u5bf9\u94f8\u9020\u6709\u4e86\u6df1\u523b\u7684\u4e86\u89e3\uff0c\u90a3\u4e48\u662f\u65f6\u5019\u4e3e\u4e00\u4e2a\u66f4\u597d\u7684\u4f8b\u5b50\u4e86\u3002\u524d\u9762\u7684\u793a\u4f8b\u73b0\u5728\u53ef\u4ee5\u5e72\u51c0\u5730\u66f4\u65b0\u4e3a\u4f7f\u7528\u6a21\u5f0f\u5339\u914d\u5f00\u5173\u8bed\u53e5\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>static void GivePromotion(Employee emp)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} was promoted!&quot;, emp.Name); switch (emp)<br \/>\n{<br \/>\ncase SalesPerson s:<br \/>\nConsole.WriteLine(&quot;{0} made {1} sale(s)!&quot;, emp.Name, s.SalesNumber); break;<br \/>\ncase Manager m:<br \/>\nConsole.WriteLine(&quot;{0} had {1} stock options...&quot;, emp.Name, m.StockOptions); break;<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>When adding a when clause to the case statement, the full definition of the object as it is cast is available for use. For example, the SalesNumber property exists only on the SalesPerson class and not the Employee class. If the cast in the first case statement succeeds, the variable s will hold an instance of a SalesPerson class, so the case statement could be updated to the following:<br \/>\n\u5c06 when \u5b50\u53e5\u6dfb\u52a0\u5230 case \u8bed\u53e5\u65f6\uff0c\u53ef\u4ee5\u4f7f\u7528\u5bf9\u8c61\u8f6c\u6362\u65f6\u7684\u5b8c\u6574\u5b9a\u4e49\u3002\u4f8b\u5982\uff0c\u5c5e\u6027\u4ec5\u5b58\u5728\u4e8e SalesPerson \u7c7b\u4e0a\uff0c\u800c\u4e0d\u5b58\u5728\u4e8e Employee \u7c7b\u4e0a\u3002\u5982\u679c\u7b2c\u4e00\u4e2a case \u8bed\u53e5\u4e2d\u7684\u5f3a\u5236\u8f6c\u6362\u6210\u529f\uff0c\u5219\u53d8\u91cf s \u5c06\u4fdd\u5b58 SalesPerson \u7c7b\u7684\u5b9e\u4f8b\uff0c\u56e0\u6b64 case \u8bed\u53e5\u53ef\u4ee5\u66f4\u65b0\u4e3a\u4ee5\u4e0b\u5185\u5bb9\uff1a<\/p>\n<p>case SalesPerson s when s.SalesNumber &gt; 5:<\/p>\n<p>These new additions to the is and switch statements provide nice improvements that help reduce the amount of code to perform matching, as the previous examples demonstrated.<br \/>\n\u6b63\u5982\u524d\u9762\u7684\u793a\u4f8b\u6240\u793a\uff0c\u5bf9 is \u548c switch \u8bed\u53e5\u7684\u8fd9\u4e9b\u65b0\u589e\u529f\u80fd\u63d0\u4f9b\u4e86\u5f88\u597d\u7684\u6539\u8fdb\uff0c\u6709\u52a9\u4e8e\u51cf\u5c11\u6267\u884c\u5339\u914d\u7684\u4ee3\u7801\u91cf\u3002<\/p>\n<h2>Discards with switch Statements (New 7.0)<\/h2>\n<p>\u4f7f\u7528\u5f00\u5173\u8bed\u53e5\u4e22\u5f03\uff08\u65b0\u7248 7.0\uff09<\/p>\n<p>Discards can also be used in switch statements, as shown in the following code:<br \/>\n\u4e22\u5f03\u4e5f\u53ef\u4ee5\u7528\u4e8e switch \u8bed\u53e5\uff0c\u5982\u4ee5\u4e0b\u4ee3\u7801\u6240\u793a\uff1a<\/p>\n<p>switch (emp)<br \/>\n{<br \/>\ncase SalesPerson s when s.SalesNumber &gt; 5:<br \/>\nConsole.WriteLine(&quot;{0} made {1} sale(s)!&quot;, emp.Name, s.SalesNumber); break;<br \/>\ncase Manager m:<br \/>\nConsole.WriteLine(&quot;{0} had {1} stock options...&quot;, emp.Name, m.StockOptions); break;<br \/>\ncase Employee _:<br \/>\nConsole.WriteLine(&quot;Unable to promote {0}. Wrong employee type&quot;, emp.Name); break;<br \/>\n}<\/p>\n<p>Every type coming in is already an Employee, so the final case statement is always true. However, as discussed when pattern matching was introduced in Chapter 3, once a match is made, the switch statement is exited. This demonstrates the importance of getting the order correct. If the final statement was moved to the top, no Employee would ever be promoted.<br \/>\n\u6bcf\u4e2a\u8fdb\u5165\u7684\u7c7b\u578b\u90fd\u5df2\u7ecf\u662f\u5458\u5de5\uff0c\u56e0\u6b64\u6700\u7ec8\u7684\u60c5\u51b5\u9648\u8ff0\u59cb\u7ec8\u4e3a\u771f\u3002\u4f46\u662f\uff0c\u6b63\u5982\u7b2c 3 \u7ae0\u4e2d\u5f15\u5165\u6a21\u5f0f\u5339\u914d\u65f6\u6240\u8ba8\u8bba\u7684\u90a3\u6837\uff0c\u4e00\u65e6\u8fdb\u884c\u4e86\u5339\u914d\uff0cswitch \u8bed\u53e5\u5c31\u4f1a\u9000\u51fa\u3002\u8fd9\u8bf4\u660e\u4e86\u6b63\u786e\u6392\u5e8f\u7684\u91cd\u8981\u6027\u3002\u5982\u679c\u6700\u7ec8\u58f0\u660e\u88ab\u79fb\u5230\u9876\u90e8\uff0c\u5219\u4e0d\u4f1a\u664b\u5347\u4efb\u4f55\u5458\u5de5\u3002<\/p>\n<h2>Understanding the Super Parent Class: System.Object<\/h2>\n<p>\u4e86\u89e3\u8d85\u7236\u7c7b\uff1aSystem.Object<\/p>\n<p>To wrap up this chapter, I\u2019d like to examine the details of the super parent class: Object. As you were reading the previous section, you might have noticed that the base classes in your hierarchies (Car, Shape, Employee) never explicitly specify their parent classes.<br \/>\n\u4e3a\u4e86\u7ed3\u675f\u672c\u7ae0\uff0c\u6211\u60f3\u68c0\u67e5\u4e00\u4e0b\u8d85\u7ea7\u7236\u7c7b\u7684\u7ec6\u8282\uff1a\u5bf9\u8c61\u3002\u5728\u9605\u8bfb\u4e0a\u4e00\u8282\u65f6\uff0c\u60a8\u53ef\u80fd\u5df2\u7ecf\u6ce8\u610f\u5230\u5c42\u6b21\u7ed3\u6784\u4e2d\u7684\u57fa\u7c7b\uff08\u6c7d\u8f66\u3001\u5f62\u72b6\u3001\u5458\u5de5\uff09\u4ece\u672a\u663e\u5f0f\u6307\u5b9a\u5176\u7236\u7c7b\u3002<\/p>\n<p>\/\/ Who is the parent of Car? class Car<br \/>\n{...}<\/p>\n<p>In the .NET Core universe, every type ultimately derives from a base class named System.Object, which can be represented by the C# object keyword (lowercase o). The Object class defines a set of common members for every type in the framework. In fact, when you do build a class that does not explicitly define its parent, the compiler automatically derives your type from Object. If you want to be clear in your intentions, you are free to define classes that derive from Object as follows (however, again, there is no need to do so):<br \/>\n\u5728 .NET Core \u9886\u57df\u4e2d\uff0c\u6bcf\u4e2a\u7c7b\u578b\u6700\u7ec8\u90fd\u6d3e\u751f\u81ea\u4e00\u4e2a\u540d\u4e3a System.Object \u7684\u57fa\u7c7b\uff0c\u8be5\u57fa\u7c7b\u53ef\u4ee5\u7528 C# object \u5173\u952e\u5b57\uff08\u5c0f\u5199 o\uff09\u8868\u793a\u3002Object \u7c7b\u4e3a\u6846\u67b6\u4e2d\u7684\u6bcf\u4e2a\u7c7b\u578b\u5b9a\u4e49\u4e00\u7ec4\u516c\u5171\u6210\u5458\u3002\u4e8b\u5b9e\u4e0a\uff0c\u5f53\u60a8\u751f\u6210\u4e00\u4e2a\u672a\u663e\u5f0f\u5b9a\u4e49\u5176\u7236\u7ea7\u7684\u7c7b\u65f6\uff0c\u7f16\u8bd1\u5668\u4f1a\u81ea\u52a8\u4ece Object \u6d3e\u751f\u60a8\u7684\u7c7b\u578b\u3002\u5982\u679c\u4f60\u60f3\u660e\u786e\u4f60\u7684\u610f\u56fe\uff0c\u4f60\u53ef\u4ee5\u81ea\u7531\u5730\u5b9a\u4e49\u4ece Object \u6d3e\u751f\u7684\u7c7b\uff0c\u5982\u4e0b\u6240\u793a\uff08\u4f46\u662f\uff0c\u540c\u6837\uff0c\u6ca1\u6709\u5fc5\u8981\u8fd9\u6837\u505a\uff09\uff1a<\/p>\n<p>\/\/ Here we are explicitly deriving from System.Object. class Car : object<br \/>\n{...}<\/p>\n<p>Like any class, System.Object defines a set of members. In the following formal C# definition, note that some of these items are declared virtual, which specifies that a given member may be overridden by a subclass, while others are marked with static (and are therefore called at the class level):<br \/>\n\u5728 .NET Core \u9886\u57df\u4e2d\uff0c\u6bcf\u4e2a\u7c7b\u578b\u6700\u7ec8\u90fd\u6d3e\u751f\u81ea\u4e00\u4e2a\u540d\u4e3a System.Object \u7684\u57fa\u7c7b\uff0c\u8be5\u57fa\u7c7b\u53ef\u4ee5\u7528 C# object \u5173\u952e\u5b57\uff08\u5c0f\u5199 o\uff09\u8868\u793a\u3002Object \u7c7b\u4e3a\u6846\u67b6\u4e2d\u7684\u6bcf\u4e2a\u7c7b\u578b\u5b9a\u4e49\u4e00\u7ec4\u516c\u5171\u6210\u5458\u3002\u4e8b\u5b9e\u4e0a\uff0c\u5f53\u60a8\u751f\u6210\u4e00\u4e2a\u672a\u663e\u5f0f\u5b9a\u4e49\u5176\u7236\u7ea7\u7684\u7c7b\u65f6\uff0c\u7f16\u8bd1\u5668\u4f1a\u81ea\u52a8\u4ece Object \u6d3e\u751f\u60a8\u7684\u7c7b\u578b\u3002\u5982\u679c\u4f60\u60f3\u660e\u786e\u4f60\u7684\u610f\u56fe\uff0c\u4f60\u53ef\u4ee5\u81ea\u7531\u5730\u5b9a\u4e49\u4ece Object \u6d3e\u751f\u7684\u7c7b\uff0c\u5982\u4e0b\u6240\u793a\uff08\u4f46\u662f\uff0c\u540c\u6837\uff0c\u6ca1\u6709\u5fc5\u8981\u8fd9\u6837\u505a\uff09\uff1a<\/p>\n<p>public class Object<br \/>\n{<br \/>\n\/\/ Virtual members.<br \/>\npublic virtual bool Equals(object obj); protected virtual void Finalize(); public virtual int GetHashCode(); public virtual string ToString();<\/p>\n<p>\/\/ Instance-level, nonvirtual members.<br \/>\npublic Type GetType();<br \/>\nprotected object MemberwiseClone();<\/p>\n<p>\/\/ Static members.<br \/>\npublic static bool Equals(object objA, object objB);<br \/>\npublic static bool ReferenceEquals(object objA, object objB);<br \/>\n}<\/p>\n<p>Table 6-1 offers a rundown of the functionality provided by some of the methods you\u2019re most likely to use.<br \/>\n\u8868 6-1 \u7b80\u8981\u4ecb\u7ecd\u4e86\u60a8\u6700\u6709\u53ef\u80fd\u4f7f\u7528\u7684\u67d0\u4e9b\u65b9\u6cd5\u6240\u63d0\u4f9b\u7684\u529f\u80fd\u3002<\/p>\n<p>Table 6-1. Core Members of System.Object<br \/>\n\u8868 6-1. System.Object \u7684\u6838\u5fc3\u6210\u5458<\/p>\n<table>\n<tr>\n<td>Instance Method of Object Class<br \/>\n        \u5bf9\u8c61\u7684\u5b9e\u4f8b\u65b9\u6cd5<\/td>\n<td>Meaning in Life<\/td>\n<\/tr>\n<tr>\n<td>Equals()<\/td>\n<td>By default, this method returns\u00a0true\u00a0only if the items being compared refer to the same item in memory. Thus,\u00a0Equals()\u00a0is used to compare object references, not the state of the object. Typically, this method is overridden to return\u00a0true\u00a0only if the objects being compared have the same internal state values (i.e., value-based semantics).Be aware that if you override\u00a0Equals(), you should also override\u00a0GetHashCode(), as these methods are used internally by\u00a0Hashtable\u00a0types to retrieve subobjects from the container.Also recall from Chapter\u00a04\u00a0that the\u00a0ValueType\u00a0class overrides this method for all structures, so they work with value-based comparisons.<br \/>\n        \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u4ec5\u5f53\u8981\u6bd4\u8f83\u7684\u9879\u5f15\u7528\u5185\u5b58\u4e2d\u7684\u540c\u4e00\u9879\u65f6\uff0c\u6b64\u65b9\u6cd5\u624d\u8fd4\u56de true\u3002\u56e0\u6b64\uff0cEquals\uff08\uff09 \u7528\u4e8e\u6bd4\u8f83\u5bf9\u8c61\u5f15\u7528\uff0c\u800c\u4e0d\u662f\u5bf9\u8c61\u7684\u72b6\u6001\u3002\u901a\u5e38\uff0c\u4ec5\u5f53\u8981\u6bd4\u8f83\u7684\u5bf9\u8c61\u5177\u6709\u76f8\u540c\u7684\u5185\u90e8\u72b6\u6001\u503c\uff08\u5373\u57fa\u4e8e\u503c\u7684\u8bed\u4e49\uff09\u65f6\uff0c\u624d\u4f1a\u91cd\u5199\u6b64\u65b9\u6cd5\u4ee5\u8fd4\u56de true\u3002\u8bf7\u6ce8\u610f\uff0c\u5982\u679c\u91cd\u5199 Equals\uff08\uff09\uff0c\u5219\u8fd8\u5e94\u8986\u76d6 GetHashCode\uff08\uff09\uff0c\u56e0\u4e3a\u8fd9\u4e9b\u65b9\u6cd5\u7531 Hashtable \u7c7b\u578b\u5728\u5185\u90e8\u7528\u4e8e\u4ece\u5bb9\u5668\u4e2d\u68c0\u7d22\u5b50\u5bf9\u8c61\u3002\u8fd8\u8981\u56de\u987e\u4e00\u4e0b\u7b2c 4 \u7ae0\uff0cValueType \u7c7b\u5bf9\u6240\u6709\u7ed3\u6784\u90fd\u91cd\u5199\u4e86\u6b64\u65b9\u6cd5\uff0c\u56e0\u6b64\u5b83\u4eec\u4f7f\u7528\u57fa\u4e8e\u503c\u7684\u6bd4\u8f83\u3002<\/td>\n<\/tr>\n<tr>\n<td>Finalize()<\/td>\n<td>For the time being, you can understand this method (when overridden) is called to free any allocated resources before the object is destroyed. I talk more about the CoreCLR garbage collection services in Chapter\u00a09.<br \/>\n        \u76ee\u524d\uff0c\u60a8\u53ef\u4ee5\u7406\u89e3\u8c03\u7528\u6b64\u65b9\u6cd5\uff08\u91cd\u5199\u65f6\uff09\u662f\u4e3a\u4e86\u5728\u9500\u6bc1\u5bf9\u8c61\u4e4b\u524d\u91ca\u653e\u4efb\u4f55\u5df2\u5206\u914d\u7684\u8d44\u6e90\u3002\u6211\u5c06\u5728\u7b2c 9 \u7ae0\u4e2d\u8be6\u7ec6\u4ecb\u7ecd CoreCLR \u5783\u573e\u56de\u6536\u670d\u52a1\u3002<\/td>\n<\/tr>\n<tr>\n<td>GetHashCode()<\/td>\n<td>This method returns an\u00a0int\u00a0that identifies a specific object instance.<br \/>\n        \u6b64\u65b9\u6cd5\u8fd4\u56de\u6807\u8bc6\u7279\u5b9a\u5bf9\u8c61\u5b9e\u4f8b\u7684 int\u3002<\/td>\n<\/tr>\n<tr>\n<td>ToString()<\/td>\n<td>This method returns a string representation of this object, using the &lt;namespace&gt;.&lt;type name&gt;\u00a0format (termed the\u00a0fully qualified name). This method will often be overridden by a subclass to return a tokenized string of name-value pairs that represent the object\u2019s internal state, rather than its fully qualified name.<br \/>\n        \u6b64\u65b9\u6cd5\u8fd4\u56de\u6b64\u5bf9\u8c61\u7684\u5b57\u7b26\u4e32\u8868\u793a\u5f62\u5f0f\uff0c\u4f7f\u7528<\u547d\u540d\u7a7a\u95f4>.<\u952e\u5165\u540d\u79f0>\u683c\u5f0f\uff08\u79f0\u4e3a\u5b8c\u5168\u9650\u5b9a\u540d\uff09\u3002\u6b64\u65b9\u6cd5\u901a\u5e38\u4f1a\u88ab\u5b50\u7c7b\u91cd\u5199\uff0c\u4ee5\u8fd4\u56de\u8868\u793a\u5bf9\u8c61\u5185\u90e8\u72b6\u6001\u7684\u540d\u79f0-\u503c\u5bf9\u7684\u6807\u8bb0\u5316\u5b57\u7b26\u4e32\uff0c\u800c\u4e0d\u662f\u5176\u5b8c\u5168\u9650\u5b9a\u540d\u79f0\u3002<\/td>\n<\/tr>\n<tr>\n<td>GetType()<\/td>\n<td>This method returns a\u00a0Type\u00a0object that fully describes the object you are currently referencing. In short, this is a runtime type identification (RTTI) method available to all objects (discussed in greater detail in Chapter\u00a017).<br \/>\n        \u6b64\u65b9\u6cd5\u8fd4\u56de\u4e00\u4e2a Type \u5bf9\u8c61\uff0c\u8be5\u5bf9\u8c61\u5b8c\u5168\u63cf\u8ff0\u5f53\u524d\u5f15\u7528\u7684\u5bf9\u8c61\u3002\u7b80\u800c\u8a00\u4e4b\uff0c\u8fd9\u662f\u4e00\u79cd\u9002\u7528\u4e8e\u6240\u6709\u5bf9\u8c61\u7684\u8fd0\u884c\u65f6\u7c7b\u578b\u8bc6\u522b\uff08RTTI\uff09\u65b9\u6cd5\uff08\u5728\u7b2c17\u7ae0\u4e2d\u6709\u66f4\u8be6\u7ec6\u7684\u8ba8\u8bba\uff09\u3002<\/td>\n<\/tr>\n<tr>\n<td>MemberwiseClone()<\/td>\n<td>This method exists to return a member-by-member copy of the current object, which is often used when cloning an object (see Chapter\u00a08).<br \/>\n        \u6b64\u65b9\u6cd5\u7684\u5b58\u5728\u662f\u4e3a\u4e86\u8fd4\u56de\u5f53\u524d\u5bf9\u8c61\u7684\u6210\u5458\u526f\u672c\uff0c\u8be5\u526f\u672c\u901a\u5e38\u5728\u514b\u9686\u5bf9\u8c61\u65f6\u4f7f\u7528\uff08\u8bf7\u53c2\u9605\u7b2c 8 \u7ae0\uff09\u3002<\/td>\n<\/tr>\n<\/table>\n<p>To illustrate some of the default behavior provided by the Object base class, create a final C# Console Application project named ObjectOverrides. Insert a new C# class type that contains the following empty class definition for a type named Person:<br \/>\n\u82e5\u8981\u8bf4\u660e Object \u57fa\u7c7b\u63d0\u4f9b\u7684\u4e00\u4e9b\u9ed8\u8ba4\u884c\u4e3a\uff0c\u8bf7\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a ObjectOverride \u7684\u6700\u7ec8 C# \u63a7\u5236\u53f0\u5e94\u7528\u7a0b\u5e8f\u9879\u76ee\u3002\u63d2\u5165\u4e00\u4e2a\u65b0\u7684 C# \u7c7b\u7c7b\u578b\uff0c\u5176\u4e2d\u5305\u542b\u540d\u4e3a Person \u7684\u7c7b\u578b\u7684\u4ee5\u4e0b\u7a7a\u7c7b\u5b9a\u4e49\uff1a<\/p>\n<p>namespace ObjectOverrides;<br \/>\n\/\/ Remember! Person extends Object. class Person<br \/>\n{<br \/>\n}<\/p>\n<p>Now, update your top-level statements to interact with the inherited members of System.Object as follows:<br \/>\n\u73b0\u5728\uff0c\u66f4\u65b0\u9876\u7ea7\u8bed\u53e5\u4ee5\u4e0e System.Object \u7684\u7ee7\u627f\u6210\u5458\u8fdb\u884c\u4ea4\u4e92\uff0c\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p>using ObjectOverrides;<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fun with System.Object <\/em><\/strong><\/strong>\\n&quot;); Person p1 = new Person();<\/p>\n<p>\/\/ Use inherited members of System.Object. Console.WriteLine(&quot;ToString: {0}&quot;, p1.ToString()); Console.WriteLine(&quot;Hash code: {0}&quot;, p1.GetHashCode()); Console.WriteLine(&quot;Type: {0}&quot;, p1.GetType());<\/p>\n<p>\/\/ Make some other references to p1. Person p2 = p1;<br \/>\nobject o = p2;<br \/>\n\/\/ Are the references pointing to the same object in memory? if (o.Equals(p1) &amp;&amp; p2.Equals(o))<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Same instance!&quot;);<br \/>\n}<br \/>\nConsole.ReadLine();<br \/>\n}<\/p>\n<p>Here is the output of the current code:<\/p>\n<p><strong><strong><em> Fun with System.Object <\/em><\/strong><\/strong> ToString: ObjectOverrides.Person Hash code: 58225482<br \/>\nType: ObjectOverrides.Person Same instance!<\/p>\n<p>Notice how the default implementation of ToString() returns the fully qualified name of the current type (ObjectOverrides.Person). As you will see later during the examination of building custom namespaces in Chapter 15, every C# project defines a \u201croot namespace,\u201d which has the same name of the project itself. Here, you created a project named ObjectOverrides; thus, the Person type and the Program. cs file have both been placed within the ObjectOverrides namespace.<br \/>\n\u8bf7\u6ce8\u610f ToString\uff08\uff09 \u7684\u9ed8\u8ba4\u5b9e\u73b0\u5982\u4f55\u8fd4\u56de\u5f53\u524d\u7c7b\u578b \uff08ObjectOverrides.Person\uff09 \u7684\u5b8c\u5168\u9650\u5b9a\u540d\u79f0\u3002\u6b63\u5982\u7a0d\u540e\u5728\u7b2c 15 \u7ae0\u4e2d\u68c0\u67e5\u6784\u5efa\u81ea\u5b9a\u4e49\u547d\u540d\u7a7a\u95f4\u65f6\u5c06\u770b\u5230\u7684\u90a3\u6837\uff0c\u6bcf\u4e2a C# \u9879\u76ee\u90fd\u5b9a\u4e49\u4e86\u4e00\u4e2a\u201c\u6839\u547d\u540d\u7a7a\u95f4\u201d\uff0c\u8be5\u547d\u540d\u7a7a\u95f4\u5177\u6709\u4e0e\u9879\u76ee\u672c\u8eab\u3002\u5728\u8fd9\u91cc\uff0c\u60a8\u521b\u5efa\u4e86\u4e00\u4e2a\u540d\u4e3a\u201c\u5bf9\u8c61\u8986\u76d6\u201d\u7684\u9879\u76ee;\u56e0\u6b64\uff0c\u4eba\u5458\u7c7b\u578b\u548c\u7a0b\u5e8f.cs\u6587\u4ef6\u90fd\u5df2\u653e\u7f6e\u5728 ObjectOverrides \u547d\u540d\u7a7a\u95f4\u4e2d\u3002<\/p>\n<p>The default behavior of Equals() is to test whether two variables are pointing to the same object in memory. Here, you create a new Person variable named p1. At this point, a new Person object is placed on the managed heap. p2 is also of type Person. However, you are not creating a new instance but rather assigning this variable to reference p1. Therefore, p1 and p2 are both pointing to the same object in memory, as is the variable o (of type object, which was thrown in for good measure). Given that p1, p2, and o all point to the same memory location, the equality test succeeds.<br \/>\nEquals\uff08\uff09 \u7684\u9ed8\u8ba4\u884c\u4e3a\u662f\u6d4b\u8bd5\u4e24\u4e2a\u53d8\u91cf\u662f\u5426\u6307\u5411\u5185\u5b58\u4e2d\u7684\u540c\u4e00\u5bf9\u8c61\u3002\u5728\u8fd9\u91cc\uff0c\u60a8\u5c06\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a p1 \u7684\u65b0 Person \u53d8\u91cf\u3002\u6b64\u65f6\uff0c\u4e00\u4e2a\u65b0\u7684 Person \u5bf9\u8c61\u653e\u7f6e\u5728\u6258\u7ba1\u5806\u4e0a\u3002p2 \u4e5f\u662f\u201c\u4eba\u201d\u7c7b\u578b\u3002\u4f46\u662f\uff0c\u60a8\u4e0d\u662f\u5728\u521b\u5efa\u65b0\u5b9e\u4f8b\uff0c\u800c\u662f\u5728\u521b\u5efa\u5c06\u6b64\u53d8\u91cf\u5206\u914d\u7ed9\u5f15\u7528 P1\u3002\u56e0\u6b64\uff0cp1 \u548c p2 \u90fd\u6307\u5411\u5185\u5b58\u4e2d\u7684\u540c\u4e00\u5bf9\u8c61\uff0c\u53d8\u91cf o\uff08\u5bf9\u8c61\u7c7b\u578b\uff0c\u4e3a\u4e86\u66f4\u597d\u5730\u6d4b\u91cf\u800c\u629b\u51fa\uff09\u4e5f\u662f\u5982\u6b64\u3002\u5047\u8bbe p1\u3001p2 \u548c o \u90fd\u6307\u5411\u76f8\u540c\u7684\u5185\u5b58\u4f4d\u7f6e\uff0c\u5219\u76f8\u7b49\u6027\u6d4b\u8bd5\u6210\u529f\u3002<\/p>\n<p>Although the canned behavior of System.Object can fit the bill in a number of cases, it is quite common for your custom types to override some of these inherited methods. To illustrate, update the Person class to support some properties representing an individual\u2019s first name, last name, and age, each of which can be set by a custom constructor.<br \/>\n\u5c3d\u7ba1 System.Object \u7684\u56fa\u5b9a\u884c\u4e3a\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b\u53ef\u4ee5\u6ee1\u8db3\u8981\u6c42\uff0c\u4f46\u81ea\u5b9a\u4e49\u7c7b\u578b\u91cd\u5199\u5176\u4e2d\u4e00\u4e9b\u7ee7\u627f\u65b9\u6cd5\u662f\u5f88\u5e38\u89c1\u7684\u3002\u4e3a\u4e86\u8fdb\u884c\u8bf4\u660e\uff0c\u8bf7\u66f4\u65b0 Person \u7c7b\u4ee5\u652f\u6301\u8868\u793a\u4e2a\u4eba\u540d\u5b57\u3001\u59d3\u6c0f\u548c\u5e74\u9f84\u7684\u67d0\u4e9b\u5c5e\u6027\uff0c\u6bcf\u4e2a\u5c5e\u6027\u90fd\u53ef\u4ee5\u7531\u81ea\u5b9a\u4e49\u6784\u9020\u51fd\u6570\u8bbe\u7f6e\u3002<\/p>\n<p>namespace ObjectOverrides;<br \/>\n\/\/ Remember! Person extends Object. class Person<br \/>\n{<br \/>\npublic string FirstName { get; set; } = &quot;&quot;; public string LastName { get; set; } = &quot;&quot;; public int Age { get; set; }<\/p>\n<p>public Person(string fName, string lName, int personAge)<br \/>\n{<br \/>\nFirstName = fName;<br \/>\nLastName = lName;<br \/>\nAge = personAge;<br \/>\n}<br \/>\npublic Person(){}<br \/>\n}<\/p>\n<h2>Overriding System.Object.ToString( )<\/h2>\n<p>\u8986\u76d6 System.Object.ToString\uff08 \uff09<\/p>\n<p>Many classes (and structures) that you create can benefit from overriding ToString() to return a string textual representation of the type\u2019s current state. This can be quite helpful for purposes of debugging (among other reasons). How you choose to construct this string is a matter of personal choice; however, a recommended approach is to separate each name-value pair with semicolons and wrap the entire string within square brackets (many types in the .NET Core base class libraries follow this approach). Consider the following overridden ToString() for your Person class:<br \/>\n\u60a8\u521b\u5efa\u7684\u8bb8\u591a\u7c7b\uff08\u548c\u7ed3\u6784\uff09\u90fd\u53ef\u4ee5\u4ece\u91cd\u5199 ToString\uff08\uff09 \u4ee5\u8fd4\u56de\u7c7b\u578b\u5f53\u524d\u72b6\u6001\u7684\u5b57\u7b26\u4e32\u6587\u672c\u8868\u793a\u5f62\u5f0f\u4e2d\u53d7\u76ca\u3002\u8fd9\u5bf9\u4e8e\u8c03\u8bd5\uff08\u4ee5\u53ca\u5176\u4ed6\u539f\u56e0\uff09\u975e\u5e38\u6709\u7528\u3002\u60a8\u9009\u62e9\u5982\u4f55\u6784\u9020\u6b64\u5b57\u7b26\u4e32\u662f\u4e2a\u4eba\u9009\u62e9\u7684\u95ee\u9898;\u4f46\u662f\uff0c\u5efa\u8bae\u7684\u65b9\u6cd5\u662f\u7528\u5206\u53f7\u5206\u9694\u6bcf\u4e2a\u540d\u79f0\/\u503c\u5bf9\uff0c\u5e76\u5c06\u6574\u4e2a\u5b57\u7b26\u4e32\u62ec\u5728\u65b9\u62ec\u53f7\u5185\uff08.NET Core \u57fa\u7c7b\u5e93\u4e2d\u7684\u8bb8\u591a\u7c7b\u578b\u90fd\u9075\u5faa\u6b64\u65b9\u6cd5\uff09\u3002\u8003\u8651\u4ee5\u4e0b\u91cd\u5199\u7684 ToString\uff08\uff09 \u7528\u4e8e\u60a8\u7684 Person \u7c7b\uff1a<\/p>\n<p>public override string ToString() =&gt; $&quot;[First Name: {FirstName}; Last Name: {LastName}; Age: {Age}]&quot;;<\/p>\n<p>This implementation of ToString() is quite straightforward, given that the Person class has only three pieces of state data. However, always remember that a proper ToString() override should also account for any data defined up the chain of inheritance.<br \/>\nToString\uff08\uff09 \u7684\u5b9e\u73b0\u975e\u5e38\u7b80\u5355\uff0c\u56e0\u4e3a Person \u7c7b\u53ea\u6709\u4e09\u6bb5\u72b6\u6001\u6570\u636e\u3002\u4f46\u662f\uff0c\u8bf7\u59cb\u7ec8\u8bb0\u4f4f\uff0c\u6b63\u786e\u7684 ToString\uff08\uff09 \u8986\u76d6\u4e5f\u5e94\u8be5\u8003\u8651\u7ee7\u627f\u94fe\u4e0a\u5b9a\u4e49\u7684\u4efb\u4f55\u6570\u636e\u3002<\/p>\n<p>When you override ToString() for a class extending a custom base class, the first order of business is to obtain the ToString() value from your parent using the base keyword. After you have obtained your parent\u2019s string data, you can append the derived class\u2019s custom information.<br \/>\n\u5f53\u60a8\u91cd\u5199\u6269\u5c55\u81ea\u5b9a\u4e49\u57fa\u7c7b\u7684\u7c7b\u7684 ToString\uff08\uff09 \u65f6\uff0c\u7b2c\u4e00\u4e2a\u4e1a\u52a1\u987a\u5e8f\u662f\u4f7f\u7528 base \u5173\u952e\u5b57\u4ece\u7236\u7ea7\u83b7\u53d6 ToString\uff08\uff09 \u503c\u3002 \u83b7\u53d6\u7236\u7c7b\u7684\u5b57\u7b26\u4e32\u6570\u636e\u540e\uff0c\u53ef\u4ee5\u8ffd\u52a0\u6d3e\u751f\u7c7b\u7684\u81ea\u5b9a\u4e49\u4fe1\u606f\u3002<\/p>\n<h2>Overriding System.Object.Equals( )<\/h2>\n<p>\u8986\u76d6\u7cfb\u7edf.\u5bf9\u8c61.\u7b49\u4e8e\uff08 \uff09<\/p>\n<p>Let\u2019s also override the behavior of Object.Equals() to work with value-based semantics. Recall that, by default, Equals() returns true only if the two objects being compared reference the same object instance in memory. For the Person class, it may be helpful to implement Equals() to return true if the two variables being compared contain the same state values (e.g., first name, last name, and age).<br \/>\n\u6211\u4eec\u8fd8\u8986\u76d6 Object.Equals\uff08\uff09 \u7684\u884c\u4e3a\u4ee5\u4f7f\u7528\u57fa\u4e8e\u503c\u7684\u8bed\u4e49\u3002\u56de\u60f3\u4e00\u4e0b\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u4ec5\u5f53\u8981\u6bd4\u8f83\u7684\u4e24\u4e2a\u5bf9\u8c61\u5f15\u7528\u5185\u5b58\u4e2d\u7684\u540c\u4e00\u5bf9\u8c61\u5b9e\u4f8b\u65f6\uff0cEquals\uff08\uff09 \u624d\u8fd4\u56de true\u3002\u5bf9\u4e8e Person \u7c7b\uff0c\u5982\u679c\u8981\u6bd4\u8f83\u7684\u4e24\u4e2a\u53d8\u91cf\u5305\u542b\u76f8\u540c\u7684\u72b6\u6001\u503c\uff08\u4f8b\u5982\uff0c\u540d\u5b57\u3001\u59d3\u6c0f\u548c\u5e74\u9f84\uff09\uff0c\u5219\u5b9e\u73b0 Equals\uff08\uff09 \u8fd4\u56de true \u53ef\u80fd\u4f1a\u6709\u6240\u5e2e\u52a9\u3002<br \/>\nFirst, notice that the incoming argument of the Equals() method is a general System.Object. Given this, your first order of business is to ensure the caller has indeed passed in a Person object and, as an extra safeguard, to make sure the incoming parameter is not a null reference.<br \/>\n\u9996\u5148\uff0c\u8bf7\u6ce8\u610f Equals\uff08\uff09 \u65b9\u6cd5\u7684\u4f20\u5165\u53c2\u6570\u662f\u4e00\u4e2a\u901a\u7528\u7684 System.Object\u3002\u9274\u4e8e\u6b64\uff0c\u60a8\u7684\u7b2c\u4e00\u4e2a\u4e1a\u52a1\u987a\u5e8f\u662f\u786e\u4fdd\u8c03\u7528\u65b9\u786e\u5b9e\u4f20\u5165\u4e86 Person \u5bf9\u8c61\uff0c\u5e76\u4e14\u4f5c\u4e3a\u989d\u5916\u7684\u4fdd\u62a4\u63aa\u65bd\uff0c\u786e\u4fdd\u4f20\u5165\u53c2\u6570\u4e0d\u662f null \u5f15\u7528\u3002<\/p>\n<p>After you have established the caller has passed you an allocated Person, one approach to implement Equals() is to perform a field-by-field comparison against the data of the incoming object to the data of the current object.<br \/>\n\u5728\u786e\u5b9a\u8c03\u7528\u65b9\u5df2\u5c06\u5df2\u5206\u914d\u7684\u4eba\u5458\u4f20\u9012\u7ed9\u60a8\u540e\uff0c\u5b9e\u73b0 Equals\uff08\uff09 \u7684\u4e00\u79cd\u65b9\u6cd5\u662f\u5bf9\u4f20\u5165\u5bf9\u8c61\u7684\u6570\u636e\u4e0e\u5f53\u524d\u5bf9\u8c61\u7684\u6570\u636e\u6267\u884c\u9010\u5b57\u6bb5\u6bd4\u8f83\u3002<\/p>\n<p>public override bool Equals(object obj)<br \/>\n{<br \/>\nif (!(obj is Person temp))<br \/>\n{<br \/>\nreturn false;<br \/>\n}<br \/>\nif (temp.FirstName == this.FirstName &amp;&amp; temp.LastName == this.LastName &amp;&amp; temp.Age == this.Age)<br \/>\n{<br \/>\nreturn true;<br \/>\n}<br \/>\nreturn false;<br \/>\n}<\/p>\n<p>Here, you are examining the values of the incoming object against the values of your internal values (note the use of the this keyword). If the names and age of each are identical, you have two objects with the same state data and, therefore, return true. Any other possibility results in returning false.<br \/>\n\u5728\u8fd9\u91cc\uff0c\u60a8\u6b63\u5728\u6839\u636e\u5185\u90e8\u503c\u7684\u503c\u68c0\u67e5\u4f20\u5165\u5bf9\u8c61\u7684\u503c\uff08\u8bf7\u6ce8\u610f this \u5173\u952e\u5b57\u7684\u4f7f\u7528\uff09\u3002\u5982\u679c\u6bcf\u4e2a\u5bf9\u8c61\u7684\u540d\u79f0\u548c\u671f\u9650\u76f8\u540c\uff0c\u5219\u6709\u4e24\u4e2a\u5bf9\u8c61\u5177\u6709\u76f8\u540c\u7684\u72b6\u6001\u6570\u636e\uff0c\u56e0\u6b64\u8fd4\u56de true\u3002\u4efb\u4f55\u5176\u4ed6\u53ef\u80fd\u6027\u90fd\u4f1a\u5bfc\u81f4\u8fd4\u56de false\u3002<\/p>\n<p>While this approach does indeed work, you can certainly imagine how labor intensive it would be to implement a custom Equals() method for nontrivial types that may contain dozens of data fields. One common shortcut is to leverage your own implementation of ToString(). If a class has a prim-and-proper implementation of ToString() that accounts for all field data up the chain of inheritance, you can simply perform a comparison of the object\u2019s string data (checking for null).<br \/>\n\u867d\u7136\u8fd9\u79cd\u65b9\u6cd5\u786e\u5b9e\u6709\u6548\uff0c\u4f46\u60a8\u5f53\u7136\u53ef\u4ee5\u60f3\u8c61\u4e3a\u53ef\u80fd\u5305\u542b\u6570\u5341\u4e2a\u6570\u636e\u5b57\u6bb5\u7684\u975e\u5e73\u51e1\u7c7b\u578b\u5b9e\u73b0\u81ea\u5b9a\u4e49 Equals\uff08\uff09 \u65b9\u6cd5\u662f\u591a\u4e48\u7684\u52b3\u52a8\u5bc6\u96c6\u3002\u4e00\u4e2a\u5e38\u89c1\u7684\u5feb\u6377\u65b9\u5f0f\u662f\u5229\u7528\u4f60\u81ea\u5df1\u7684 ToString\uff08\uff09 \u5b9e\u73b0\u3002\u5982\u679c\u4e00\u4e2a\u7c7b\u5177\u6709 ToString\uff08\uff09 \u7684\u539f\u59cb\u548c\u6b63\u786e\u7684\u5b9e\u73b0\uff0c\u8be5\u5b9e\u73b0\u8003\u8651\u4e86\u7ee7\u627f\u94fe\u4e0a\u7684\u6240\u6709\u5b57\u6bb5\u6570\u636e\uff0c\u5219\u53ea\u9700\u5bf9\u5bf9\u8c61\u7684\u5b57\u7b26\u4e32\u6570\u636e\u8fdb\u884c\u6bd4\u8f83\uff08\u68c0\u67e5 null\uff09\u3002<\/p>\n<p>\/\/ No need to cast &quot;obj&quot; to a Person anymore,<br \/>\n\/\/ as everything has a ToString() method. public override bool Equals(object obj)<br \/>\n=&gt; obj?.ToString() == ToString();<\/p>\n<p>Notice in this case that you no longer need to check whether the incoming argument is of the correct type (a Person, in this example), as everything in .NET supports a ToString() method. Even better, you no longer need to perform a property-by-property equality check, as you are now simply testing the value returned from ToString().<br \/>\n\u8bf7\u6ce8\u610f\uff0c\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u4e0d\u518d\u9700\u8981\u68c0\u67e5\u4f20\u5165\u53c2\u6570\u7684\u7c7b\u578b\u662f\u5426\u6b63\u786e\uff08\u5728\u672c\u4f8b\u4e2d\u4e3a Person\uff09\uff0c\u56e0\u4e3a .NET \u4e2d\u7684\u6240\u6709\u5185\u5bb9\u90fd\u652f\u6301 ToString\uff08\uff09 \u65b9\u6cd5\u3002\u66f4\u597d\u7684\u662f\uff0c\u60a8\u4e0d\u518d\u9700\u8981\u6267\u884c\u9010\u4e2a\u5c5e\u6027\u7684\u76f8\u7b49\u6027\u68c0\u67e5\uff0c\u56e0\u4e3a\u60a8\u73b0\u5728\u53ea\u9700\u6d4b\u8bd5\u4ece ToString\uff08\uff09 \u8fd4\u56de\u7684\u503c\u3002<\/p>\n<h2>Overriding System.Object.GetHashCode()<\/h2>\n<p>\u8986\u76d6System.Object.GetHashCode\uff08\uff09<\/p>\n<p>When a class overrides the Equals() method, you should also override the default implementation of GetHashCode(). Simply put, a hash code is a numerical value that represents an object as a particular state. For example, if you create two string variables that hold the value Hello, you will obtain the same hash code. However, if one of the string objects were in all lowercase (hello), you would obtain different hash codes.<br \/>\n\u5f53\u7c7b\u91cd\u5199 Equals\uff08\uff09 \u65b9\u6cd5\u65f6\uff0c\u8fd8\u5e94\u8986\u76d6 GetHashCode\uff08\uff09 \u7684\u9ed8\u8ba4\u5b9e\u73b0\u3002\u7b80\u5355\u5730\u8bf4\uff0c\u54c8\u5e0c\u7801\u662f\u4e00\u4e2a\u6570\u503c\uff0c\u8868\u793a\u5bf9\u8c61\u4f5c\u4e3a\u7279\u5b9a\u72b6\u6001\u3002\u4f8b\u5982\uff0c\u5982\u679c\u521b\u5efa\u4e24\u4e2a\u4fdd\u5b58\u503c Hello \u7684\u5b57\u7b26\u4e32\u53d8\u91cf\uff0c\u5219\u5c06\u83b7\u5f97\u76f8\u540c\u7684\u54c8\u5e0c\u4ee3\u7801\u3002\u4f46\u662f\uff0c\u5982\u679c\u5176\u4e2d\u4e00\u4e2a\u5b57\u7b26\u4e32\u5bf9\u8c61\u5168\u90e8\u4e3a\u5c0f\u5199 \uff08hello\uff09\uff0c\u60a8\u5c06\u83b7\u5f97\u4e0d\u540c\u7684\u54c8\u5e0c\u4ee3\u7801\u3002<\/p>\n<p>By default, System.Object.GetHashCode() uses your object\u2019s current location in memory to yield the hash value. However, if you are building a custom type that you intend to store in a Hashtable type (within the System.Collections namespace), you should always override this member, as the Hashtable will be internally invoking Equals() and GetHashCode() to retrieve the correct object.<br \/>\n\u5f53\u7c7b\u91cd\u5199 Equals\uff08\uff09 \u65b9\u6cd5\u65f6\uff0c\u8fd8\u5e94\u8986\u76d6 GetHashCode\uff08\uff09 \u7684\u9ed8\u8ba4\u5b9e\u73b0\u3002\u7b80\u5355\u5730\u8bf4\uff0c\u54c8\u5e0c\u7801\u662f\u4e00\u4e2a\u6570\u503c\uff0c\u8868\u793a\u5bf9\u8c61\u4f5c\u4e3a\u7279\u5b9a\u72b6\u6001\u3002\u4f8b\u5982\uff0c\u5982\u679c\u521b\u5efa\u4e24\u4e2a\u4fdd\u5b58\u503c Hello \u7684\u5b57\u7b26\u4e32\u53d8\u91cf\uff0c\u5219\u5c06\u83b7\u5f97\u76f8\u540c\u7684\u54c8\u5e0c\u4ee3\u7801\u3002\u4f46\u662f\uff0c\u5982\u679c\u5176\u4e2d\u4e00\u4e2a\u5b57\u7b26\u4e32\u5bf9\u8c61\u5168\u90e8\u4e3a\u5c0f\u5199 \uff08hello\uff09\uff0c\u60a8\u5c06\u83b7\u5f97\u4e0d\u540c\u7684\u54c8\u5e0c\u4ee3\u7801\u3002<\/p>\n<p>\u25a0 Note To be more specific, the System.Collections.Hashtable class calls GetHashCode() internally to gain a general idea where the object is located, but a subsequent (internal) call to Equals() determines the exact match.<br \/>\n\u6ce8\u610f \u66f4\u5177\u4f53\u5730\u8bf4\uff0cSystem.Collections.Hashtable \u7c7b\u5728\u5185\u90e8\u8c03\u7528 GetHashCode\uff08\uff09 \u4ee5\u83b7\u5f97\u5bf9\u8c61\u6240\u5728\u7684\u5927\u81f4\u6982\u5ff5\uff0c\u4f46\u968f\u540e\uff08\u5185\u90e8\uff09\u5bf9 Equals\uff08\uff09 \u7684\u8c03\u7528\u51b3\u5b9a\u4e86\u5b8c\u5168\u5339\u914d\u3002<\/p>\n<p>Although you are not going to place your Person into a System.Collections.Hashtable in this example, for completion let\u2019s override GetHashCode(). There are many algorithms that can be used to create a hash code\u2014some fancy, others not so fancy. Most of the time, you are able to generate a hash code value by leveraging the System.String\u2019s GetHashCode() implementation.<br \/>\n\u6ce8\u610f \u66f4\u5177\u4f53\u5730\u8bf4\uff0cSystem.Collections.Hashtable \u7c7b\u5728\u5185\u90e8\u8c03\u7528 GetHashCode\uff08\uff09 \u4ee5\u83b7\u5f97\u5bf9\u8c61\u6240\u5728\u7684\u5927\u81f4\u6982\u5ff5\uff0c\u4f46\u968f\u540e\uff08\u5185\u90e8\uff09\u5bf9 Equals\uff08\uff09 \u7684\u8c03\u7528\u51b3\u5b9a\u4e86\u5b8c\u5168\u5339\u914d\u3002<\/p>\n<p>Given that the String class already has a solid hash code algorithm that is using the character data of the String to compute a hash value, if you can identify a piece of field data on your class that should be unique for all instances (such as a Social Security number), simply call GetHashCode() on that point of field data. Thus, if the Person class defined an SSN property, you could author the following code:<br \/>\n\u5047\u8bbe String \u7c7b\u5df2\u7ecf\u6709\u4e00\u4e2a\u53ef\u9760\u7684\u54c8\u5e0c\u4ee3\u7801\u7b97\u6cd5\uff0c\u8be5\u7b97\u6cd5\u4f7f\u7528 String \u7684\u5b57\u7b26\u6570\u636e\u6765\u8ba1\u7b97\u54c8\u5e0c\u503c\uff0c\u5982\u679c\u60a8\u53ef\u4ee5\u8bc6\u522b\u7c7b\u4e0a\u7684\u4e00\u6bb5\u5b57\u6bb5\u6570\u636e\uff0c\u8be5\u5b57\u6bb5\u6570\u636e\u5e94\u8be5\u662f\u5bf9\u4e8e\u6240\u6709\u5b9e\u4f8b\uff08\u4f8b\u5982\u793e\u4f1a\u4fdd\u9669\u53f7\uff09\u90fd\u662f\u552f\u4e00\u7684\uff0c\u53ea\u9700\u5728\u8be5\u5b57\u6bb5\u6570\u636e\u70b9\u4e0a\u8c03\u7528 GetHashCode\uff08\uff09\u3002 \u56e0\u6b64\uff0c\u5982\u679c Person \u7c7b\u5b9a\u4e49\u4e86 SSN \u5c5e\u6027\uff0c\u5219\u53ef\u4ee5\u7f16\u5199\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<p>\/\/ Assume we have an SSN property as so. class Person<br \/>\n{<br \/>\npublic string SSN {get; } = &quot;&quot;;<br \/>\npublic Person(string fName, string lName, int personAge, string ssn)<br \/>\n{<\/p>\n<p>FirstName = fName;<br \/>\nLastName = lName;<br \/>\nAge = personAge;<br \/>\nSSN = ssn;<br \/>\n}<br \/>\n\/\/ Return a hash code based on unique string data. public override int GetHashCode() =&gt; SSN.GetHashCode();<br \/>\n}<\/p>\n<p>If you use a read-write property for the basis of the hash code, you will receive a warning. Once an object is created, the hash code should be immutable. In the previous example, the SSN property has only a get method, which makes the property read-only, and can be set only in the constructor.<br \/>\n\u5982\u679c\u4f7f\u7528\u8bfb\u5199\u5c5e\u6027\u4f5c\u4e3a\u54c8\u5e0c\u4ee3\u7801\u7684\u57fa\u7840\uff0c\u5219\u4f1a\u6536\u5230\u8b66\u544a\u3002\u521b\u5efa\u5bf9\u8c61\u540e\uff0c\u54c8\u5e0c\u4ee3\u7801\u5e94\u8be5\u662f\u4e0d\u53ef\u53d8\u7684\u3002\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\uff0cSSN \u5c5e\u6027\u53ea\u6709\u4e00\u4e2a get \u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u4f7f\u8be5\u5c5e\u6027\u6210\u4e3a\u53ea\u8bfb\u7684\uff0c\u5e76\u4e14\u53ea\u80fd\u5728\u6784\u9020\u51fd\u6570\u4e2d\u8bbe\u7f6e\u3002<\/p>\n<p>If you cannot find a single point of unique string data but you have overridden ToString() (which satisfies the read-only convention), call GetHashCode() on your own string representation.<br \/>\n\u5982\u679c\u627e\u4e0d\u5230\u552f\u4e00\u5b57\u7b26\u4e32\u6570\u636e\u7684\u5355\u4e2a\u70b9\uff0c\u4f46\u5df2\u8986\u76d6 ToString\uff08\uff09\uff08\u6ee1\u8db3\u53ea\u8bfb\u7ea6\u5b9a\uff09\uff0c\u8bf7\u5728\u81ea\u5df1\u7684\u5b57\u7b26\u4e32\u8868\u793a\u5f62\u5f0f\u4e0a\u8c03\u7528 GetHashCode\uff08\uff09\u3002<\/p>\n<p>\/\/ Return a hash code based on the person's ToString() value. public override int GetHashCode() =&gt; ToString().GetHashCode();<\/p>\n<h2>Testing Your Modified Person Class<\/h2>\n<p>\u6d4b\u8bd5\u4fee\u6539\u540e\u7684\u4eba\u5458\u7c7b<\/p>\n<p>Now that you have overridden the virtual members of Object, update the top-level statements to test your updates.<br \/>\n\u73b0\u5728\uff0c\u60a8\u5df2\u7ecf\u8986\u76d6\u4e86 Object \u7684\u865a\u62df\u6210\u5458\uff0c\u8bf7\u66f4\u65b0\u9876\u7ea7\u8bed\u53e5\u4ee5\u6d4b\u8bd5\u66f4\u65b0\u3002<br \/>\n...<br \/>\n\/\/ NOTE: We want these to be identical to test<br \/>\n\/\/ the Equals() and GetHashCode() methods.<br \/>\nPerson p1 = new Person(&quot;Homer&quot;, &quot;Simpson&quot;, 50, &quot;111-11-1111&quot;); Person p2 = new Person(&quot;Homer&quot;, &quot;Simpson&quot;, 50, &quot;111-11-1111&quot;);<br \/>\n\/\/ Get stringified version of objects. Console.WriteLine(&quot;p1.ToString() = {0}&quot;, p1.ToString()); Console.WriteLine(&quot;p2.ToString() = {0}&quot;, p2.ToString());<\/p>\n<p>\/\/ Test overridden Equals().<br \/>\nConsole.WriteLine(&quot;p1 = p2?: {0}&quot;, p1.Equals(p2));<\/p>\n<p>\/\/ Test hash codes.<br \/>\n\/\/still using the hash of the SSN<br \/>\nConsole.WriteLine(&quot;Same hash codes?: {0}&quot;, p1.GetHashCode() == p2.GetHashCode()); Console.WriteLine();<\/p>\n<p>\/\/ Change age of p2 and test again. p2.Age = 45;<br \/>\nConsole.WriteLine(&quot;p1.ToString() = {0}&quot;, p1.ToString()); Console.WriteLine(&quot;p2.ToString() = {0}&quot;, p2.ToString()); Console.WriteLine(&quot;p1 = p2?: {0}&quot;, p1.Equals(p2));<br \/>\n\/\/still using the hash of the SSN<br \/>\nConsole.WriteLine(&quot;Same hash codes?: {0}&quot;, p1.GetHashCode() == p2.GetHashCode()); Console.ReadLine();<\/p>\n<p>The output is shown here:<br \/>\n\u8f93\u51fa\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p><strong><strong><em> Fun with System.Object <\/em><\/strong><\/strong><br \/>\np1.ToString() = [First Name: Homer; Last Name: Simpson; Age: 50] p2.ToString() = [First Name: Homer; Last Name: Simpson; Age: 50] p1 = p2?: True<br \/>\nSame hash codes?: True<\/p>\n<p>p1.ToString() = [First Name: Homer; Last Name: Simpson; Age: 50] p2.ToString() = [First Name: Homer; Last Name: Simpson; Age: 45] p1 = p2?: False<br \/>\nSame hash codes?: True<\/p>\n<h2>Using the Static Members of System.Object<\/h2>\n<p>\u4f7f\u7528 System.Object \u7684\u9759\u6001\u6210\u5458<\/p>\n<p>In addition to the instance-level members you have just examined, System.Object does define two static members that also test for value-based or reference-based equality. Consider the following code:<br \/>\n\u9664\u4e86\u521a\u521a\u68c0\u67e5\u7684\u5b9e\u4f8b\u7ea7\u6210\u5458\u4e4b\u5916\uff0cSystem.Object \u8fd8\u5b9a\u4e49\u4e86\u4e24\u4e2a\u9759\u6001\u6210\u5458\uff0c\u5b83\u4eec\u8fd8\u6d4b\u8bd5\u57fa\u4e8e\u503c\u6216\u57fa\u4e8e\u5f15\u7528\u7684\u76f8\u7b49\u6027\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<p>static void StaticMembersOfObject()<br \/>\n{<br \/>\n\/\/ Static members of System.Object.<br \/>\nPerson p3 = new Person(&quot;Sally&quot;, &quot;Jones&quot;, 4); Person p4 = new Person(&quot;Sally&quot;, &quot;Jones&quot;, 4);<br \/>\nConsole.WriteLine(&quot;P3 and P4 have same state: {0}&quot;, object.Equals(p3, p4)); Console.WriteLine(&quot;P3 and P4 are pointing to same object: {0}&quot;,<br \/>\nobject.ReferenceEquals(p3, p4));<br \/>\n}<\/p>\n<p>Here, you are able to simply send in two objects (of any type) and allow the System.Object class to determine the details automatically.<br \/>\n\u9664\u4e86\u521a\u521a\u68c0\u67e5\u7684\u5b9e\u4f8b\u7ea7\u6210\u5458\u4e4b\u5916\uff0cSystem.Object \u8fd8\u5b9a\u4e49\u4e86\u4e24\u4e2a\u9759\u6001\u6210\u5458\uff0c\u5b83\u4eec\u8fd8\u6d4b\u8bd5\u57fa\u4e8e\u503c\u6216\u57fa\u4e8e\u5f15\u7528\u7684\u76f8\u7b49\u6027\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b\u4ee3\u7801\uff1a<\/p>\n<p>The output (when called from the top-level statements) is shown here:<br \/>\n\u8f93\u51fa\uff08\u4ece\u9876\u7ea7\u8bed\u53e5\u8c03\u7528\u65f6\uff09\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<p><strong><strong><em> Fun with System.Object <\/em><\/strong><\/strong> P3 and P4 have the same state: True<br \/>\nP3 and P4 are pointing to the same object: False<\/p>\n<h2>Summary<\/h2>\n<p>\u603b\u7ed3<br \/>\nThis chapter explored the role and details of inheritance and polymorphism. Over these pages you were introduced to numerous new keywords and tokens to support each of these techniques. For example, recall that the colon token is used to establish the parent class of a given type. Parent types are able to define any number of virtual and\/or abstract members to establish a polymorphic interface. Derived types override such members using the override keyword.<br \/>\n\u672c\u7ae0\u63a2\u8ba8\u4e86\u9057\u4f20\u548c\u591a\u6001\u6027\u7684\u4f5c\u7528\u548c\u7ec6\u8282\u3002\u5728\u8fd9\u4e9b\u9875\u9762\u4e0a\uff0c\u5411\u60a8\u4ecb\u7ecd\u4e86\u8bb8\u591a\u65b0\u7684\u5173\u952e\u5b57\u548c\u4ee4\u724c\uff0c\u4ee5\u652f\u6301\u6bcf\u79cd\u6280\u672f\u3002\u4f8b\u5982\uff0c\u56de\u60f3\u4e00\u4e0b\uff0c\u5192\u53f7\u6807\u8bb0\u7528\u4e8e\u5efa\u7acb\u7ed9\u5b9a\u7c7b\u578b\u7684\u7236\u7c7b\u3002\u7236\u7c7b\u578b\u80fd\u591f\u5b9a\u4e49\u4efb\u610f\u6570\u91cf\u7684\u865a\u62df\u548c\/\u6216\u62bd\u8c61\u6210\u5458\u6765\u5efa\u7acb\u591a\u6001\u63a5\u53e3\u3002\u6d3e\u751f\u7c7b\u578b\u4f7f\u7528 override \u5173\u952e\u5b57\u91cd\u5199\u6b64\u7c7b\u6210\u5458\u3002<\/p>\n<p>In addition to building numerous class hierarchies, this chapter also examined how to explicitly cast between base and derived types and wrapped up by diving into the details of the cosmic parent class in the .NET base class libraries: System.Object.<br \/>\n\u9664\u4e86\u6784\u5efa\u5927\u91cf\u7c7b\u5c42\u6b21\u7ed3\u6784\u5916\uff0c\u672c\u7ae0\u8fd8\u7814\u7a76\u4e86\u5982\u4f55\u5728\u57fa\u7c7b\u578b\u548c\u6d3e\u751f\u7c7b\u578b\u4e4b\u95f4\u663e\u5f0f\u8f6c\u6362\uff0c\u5e76\u901a\u8fc7\u6df1\u5165\u7814\u7a76.NET \u57fa\u7c7b\u5e93\uff1aSystem.Object\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>CHAPTER 6 Understanding Inheritance and Polymorphism \u7b2c6\u7ae0 \u4e86\u89e3\u7ee7\u627f\u548c\u591a\u6001\u6027 Chapter 5 examined the first pillar of OOP: encapsulation. At that time, you learned how to build a single well-defined class type with constructors and various members (fields, properties, methods, constants, and read-only fields). This chapter will focus on the remaining two pillars of OOP: inheritance and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[22],"class_list":["post-298","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-pro-csharp10-with-net6"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/298","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=298"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/298\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=298"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=298"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=298"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}