Speaking of base classes, it is important to keep in mind that the .NET platform demands that a given class have exactly one direct base class.
说到基类,要记住很重要的一点:.NET平台要求一个类只能有一个直接基类.
While a class can have only one direct base class, it is permissible for an interface to directly derive from multiple interfaces.
虽然一个类只能有一个直接基类,但是一个接口可以直接从多个接口派生.
The sealed Keyword
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.
// This class cannot be extended!sealed class MiniVan : Car{}
If you (or a teammate) were to attempt to derive from this class, you would receive a compile-time error:
// Error! Cannot extend// a class marked with the sealed keyword!class DeluxeMiniVan : MiniVan{ }
sealed关键字
C#提供了另外一个关键字sealed来防止发生继承.如果我们将类标记为sealed,编译器将不允许我们从这个类型派生.
// 这个类不会被扩展!sealed class MiniVan : Car{}
如果尝试从这个类派生的话,就会收到一个编译时错误:
// 错误!不能扩展用sealed关键字标记的类!
// 错误!不能扩展用sealed关键字标记的类!class DeluxeMiniVan : MiniVan{}
说明 在第4章中我们已经知道,C#结构总是隐式密封的。因此,我们永远不可以从结构继承结构,从类继承结构或者从结构继承类。
The syntax used to nest a type is quite straightforward:
public class OuterClass{ // A public nested type can be used by anybody. public class PublicInnerClass { } // A private nested type can only be used by members // of the containing class. private class PrivateInnerClass { }}
Although the syntax is clean, understanding why you might do this is not readily apparent.
To understand this technique, ponder the following traits of nesting a type:
• Nested types allow you to gain complete control over the access level of the inner type, as
they may be declared privately (recall that nonnested classes cannot be declared using the
private keyword).
• Because a nested type is a member of the containing class, it can access private members of
the containing class.
• Oftentimes, a nested type is only useful as a helper for the outer class, and is not intended for
use by the outside world.
嵌套类型的语法非常简单明了:
public class OuterClass{ //公共嵌套类型可以被任何人使用. public class PublicInnerClass { } // 私有嵌套只可以被包含类的成员使用. private class PrivateInnerClass { }}
尽管语法很简洁,但是要理解为什么这么做却不那么简单。要理解这项技术,考虑如下嵌套类型的特征。
- 通过嵌套类型可以完全控制内部类型的访问级别,也就是可以声明为私有的(回忆一下,非嵌套类不能使用private关键字来声明)。
- 由于嵌套类型是包含类的成员,所以它可以访问包含类的私有成员。
- 通常,嵌套类型只用作外部类的辅助方法,而不是为外部世界所准备的。
The nesting process can be as “deep” as you require. For example, assume we wish 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, we could nest the enumeration as follows:
// Employee nests BenefitPackage.public partial class Employee{ // BenefitPackage nests BenefitPackageLevel. public class BenefitPackage { public enum BenefitPackageLevel { Standard, Gold, Platinum } public double ComputePayDeduction() { return 125.0; } } ...}
Because of the nesting relationships, note how we are required to make use of this enumeration:
static void Main(string[] args){ ... // Define my benefit level. Employee.BenefitPackage.BenefitPackageLevel myBenefitLevel = Employee.BenefitPackage.BenefitPackageLevel.Platinum; Console.ReadLine();}
嵌套的“深度”可以按需设定。例如,假设希望创建一个名为BenefitPackageLevel的枚举,它列举了员工可以选择的各种保险金级别。为了通过编程强制Employee, BenefitPackage, and BenefitPackageLevel之间的紧密联系,我们可以按如下所示嵌套枚举:
// Employee 嵌套BenefitPackage.public partial class Employee{ // BenefitPackage 嵌套BenefitPackageLevel. public class BenefitPackage { public enum BenefitPackageLevel { Standard, Gold, Platinum } public double ComputePayDeduction() { return 125.0; } } ...}
在这种嵌套关系里需要注意如何使用枚举:
static void Main(string[] args){ ... // 定义福利等级. Employee.BenefitPackage.BenefitPackageLevel myBenefitLevel = Employee.BenefitPackage.BenefitPackageLevel.Platinum; Console.ReadLine();}
If a base class wishes 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:
partial class Employee{ // This method can now be 'overridden' by a derived class. public virtual void GiveBonus(float amount) { currPay += amount; } ...}
virtual和override关键字
如果基类希望定义可以(不是必须的)由子类重写的方法,就必须制定子类是虚的:
partial class Employee{ // 这个方法现在可以由派生类'重写'. public virtual void GiveBonus(float amount) { currPay += amount; } ...}
class SalesPerson : Employee{ ... // A salesperson's bonus is influenced by the number of sales. public override void GiveBonus(float amount) { int salesBonus = 0; if (numberOfSales >= 0 && numberOfSales <= 100) { salesBonus = 10; } else { if (numberOfSales >= 101 && numberOfSales <= 200) { salesBonus = 15; } else { salesBonus = 20; } } base.GiveBonus(amount * salesBonus); }}class Manager : Employee{ ... public override void GiveBonus(float amount) { base.GiveBonus(amount); Random r = new Random(); numberOfOptions += r.Next(500); }}
如果子类希望改变虚方法的实现细节,就必须使用override关键字。
class SalesPerson : Employee{ ... // 销售人员的奖金受销售量的影响. public override void GiveBonus(float amount) { int salesBonus = 0; if (numberOfSales >= 0 && numberOfSales <= 100) { salesBonus = 10; } else { if (numberOfSales >= 101 && numberOfSales <= 200) { salesBonus = 15; } else { salesBonus = 20; } } base.GiveBonus(amount * salesBonus); }}class Manager : Employee{ ... public override void GiveBonus(float amount) { base.GiveBonus(amount); Random r = new Random(); numberOfOptions += r.Next(500); }}
Sometimes you may not wish to seal an entire class, but simply want to prevent derived types from overriding particular virtual methods.
// SalesPerson has sealed the GiveBonus() method!class SalesPerson : Employee{ ... public override sealed void GiveBonus(float amount) { ... }}
Here, SalesPerson has indeed overridden the virtual GiveBonus()method defined in the Employee class; however, it has explicitly marked it as sealed. Thus, if we attempted to override this method in the PTSalesPerson class:
sealed class PTSalesPerson : SalesPerson{ public PTSalesPerson(string fullName, int age, int empID, float currPay, string ssn, int numbOfSales) : base(fullName, age, empID, currPay, ssn, numbOfSales) { } // No bonus for you! Error! public override void GiveBonus(float amount) { // Rats. Can't change this method any further. }}
we receive compile-time errors.
密封虚成员
有时我们不希望密封整个类,而只希望防止派生类型来重写某个虚方法。
// SalesPerson 密封了GiveBonus() 方法!class SalesPerson : Employee{ ... public override sealed void GiveBonus(float amount) { ... }}
在这里,SalesPerson确实重写了定义在Employee类中的GiveBonus()虚方法,然而它显示标记为密封的,因此如果按如下所示尝试在PTSalesPerson类中重写这个方法:
sealed class PTSalesPerson : SalesPerson{ public PTSalesPerson(string fullName, int age, int empID, float currPay, string ssn, int numbOfSales) : base(fullName, age, empID, currPay, ssn, numbOfSales) { } // 你没有奖金!错误! public override void GiveBonus(float amount) { // 不能再改变这个方法. }}
我们会收到编译时错误。
Understanding Abstract Classes
Given that many base classes tend to be rather nebulous entities, a far better design for our example is to prevent the ability to directly create a new Employee object in code. In C#, you can enforce this programmatically by using the abstract keyword, thus creating an abstract base class:
// Update the Employee class as abstract// to prevent direct instantiation.abstract partial class Employee{...}
With this, if you now attempt to create an instance of the Employee class, you are issued a compile-time error:
// Error! Cannot create an abstract class!Employee X = new Employee();
由于很多基类都是比较模糊的实体,好的设计师会防止在代码中直接创建新的Employee对象。在C#中,我们可以使用abstract关键字来强制这种编程方式,因此创建一个抽象基类:
//将Employee类更新为抽象类来防止直接实例化.abstract partial class Employee{ ...}
这样,如果现在尝试创建Employee类的实例,就会收到一个编译时错误:
// 错误!不能创建抽象类!Employee X = new Employee();
Abstract members can be used whenever you wish to define a member that does not supply a default implementation. By doing so, you enforce a polymorphic interface on each descendent, leaving them to contend with the task of providing the details behind your abstract methods.
An abstract base class’ s polymorphic interface simply refers to its set of virtual and abstract methods.
构建多态接口
要定义没有提供默认实现的成员时可以使用抽象成员。这样做就强制了每一个后代具有多态接口,它们需要自己处理抽象方法的细节。
抽象基类的多态接口只是指一组虚的或者抽象的方法。
Subclasses are never required to override virtual methods
子类重写虚方法不是必须的。
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.
// Force all child classes to define how to be rendered.public abstract class Shape{ public abstract void Draw(); ...}
要强制每一个子类重写Draw()方法,我们可以把Draw()定义为Shape类的一个抽象方法,也就是说我们没有提供默认的实现。
// 强制所有子类来定义如何被呈现.public abstract class Shape{ public abstract void Draw(); ...}
说明 抽象方法只可以定义在抽象类中。如果不是这样的话,就会收到编译错误。
Given this, we are now obligated to override the Draw()method in the Circle class. If you do not, Circle is also assumed to be a noncreatable abstract type that must be adorned with the abstract keyword (which is obviously not very useful in this example). Here is the code update:
// If we did not implement the abstract Draw() method, Circle would also be// considered abstract, and would have to be marked abstract!class Circle : Shape{ public Circle() { } public Circle(string name) : base(name) { } public override void Draw() { Console.WriteLine("Drawing {0} the Circle", shapeName); }}
因此,我们就必须在Circle类中重写Draw()方法。如果不这样的话,Circle就应该是不可创建的抽象类型,必须使用abstract关键字来修饰(对于这个示例很明显不适合)。在这里是更新后的代码:
//如果不实现Draw()方法,Circle也必须是抽象的,我们必须标记abstract!class Circle : Shape{ public Circle() { } public Circle(string name) : base(name) { } public override void Draw() { Console.WriteLine("Drawing {0} the Circle", shapeName); }}
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’s version.
成员投影
如果派生类定义的成员和定义在基类中的成员一致,派生类就投影了父类的版本。
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’ s implementation is intentionally designed to hide the parent’s version.
// This class extends Circle and hides the inherited Draw() method.class ThreeDCircle : Circle{ // Hide any Draw() implementation above me. public new void Draw() { Console.WriteLine("Drawing a 3D Circle"); }}
You can also apply the new keyword to any member type inherited from a base class (field, constant, static member, property, etc.).
// This class extends Circle and hides the inherited Draw() method.class ThreeDCircle : Circle{ // Hide the shapeName field above me. protected new string shapeName; // Hide any Draw() implementation above me. public new void Draw() { Console.WriteLine("Drawing a 3D Circle"); }}
我们可以为派生类型(在这里是ThreeDCircle)的Draw()成员添加new关键字。这样就显式声明派生类型是实现故意设计为隐藏父类的版本。(如果是虚方法,可以用override关键字标记为重写)
// 这个类扩展了 Circle 并隐藏了继承的Draw() 方法.class ThreeDCircle : Circle{ // 隐藏任何在我之上的 Draw() 实现. public new void Draw() { Console.WriteLine("Drawing a 3D Circle"); }}
我们还可以把new关键字应用到任何从基类继承的成员类型中(字段、常量、静态方法、属性等)。
//这个类扩展了Circle并隐藏了继承的Draw()方法.class ThreeDCircle : Circle{ // 隐藏任何在我之上的shapeName字段. protected new string shapeName; // 隐藏任何在我之上的Draw()实现. public new void Draw() { Console.WriteLine("Drawing a 3D Circle"); }}
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:
// Use 'as' to test compatability.Hexagon hex2 = frank as Hexagon;if (hex2 == null) Console.WriteLine("Sorry, frank is not a Hexagon...");
C#的as关键字
C#提供了as关键字在运行时快速检测某个类型是否和另外一个兼容。如果我们使用as关键字,就可以通过检测null返回值来检测兼容性。考虑如下代码:
//使用“as”来测试兼容性.Hexagon hex2 = frank as Hexagon;if (hex2 == null) Console.WriteLine("Sorry, frank is not a Hexagon...");
Instance Method of Object Class | Meaning in Life |
Equals() | By default, this method returns true only if the items being compared refer to the exact same item in memory. Thus, Equals() is used to compare object references, not the state of the object. Typically, this method is overridden to return true only if the objects being compared have the same internal state values (that is, value-based semantics). Be aware that if you override Equals(), you should also override GetHashCode(), as these methods are used internally by Hashtable types to retrieve subobjects from the container |
GetHashCode() | This method returns an int that identifies a specific object instance. |
GetType() | This method returns a Type object that fully describes the object you are currently referencing. In short, this is a Runtime Type Identification (RTTI) method available to all objects |
ToString() | This method returns a string representation of this object, using the <namespace>.<type name> format (termed the fully qualified name). This method can be overridden by a subclass to return a tokenized string of name/value pairs that represent the object’s internal state, rather than its fully qualified name. |
Finalize() | For the time being, you can understand this method (when overridden) is called to free any allocated resources before the object is destroyed. |
MemberwiseClone() | This method exists to return a member-by-member copy of the current object, which is often used when cloning an object |
System.Object的核心成员
对象类的实例方法 | 作用 |
Equals() | 默认情况下,如果被比较的项指向内存中同一个项,则方法会返回true。因此,Equals()用于比较对象引用,而不是对象的状态。大多数情况下,这个方法会被重写为:如果被比较的对象有相同的内部状态值(也就是基于值的语义),则返回true。要知道,如果重写Equals(),则还需要重写GetHashCode(),因为这些方法在内部用于Hashtable类型从容器获取子对象 |
GetHashCode() | 这个方法返回int来标识指定的对象实例 |
GetType() | 这个方法返回Type对象,它完整描述当前指向的对象。简而言之,这是所有对象都可用的运行时类型标识方法 |
ToString() | 这个方法是用<namespace>.<type name> 格式(叫做完全限定名)返回对象的字符串表示。这个方法可以被子类重写来返回名称/值对的标识字符串以表示对象的内部状态,而不是它的完全限定名 |
Finalize() | 这个方法(在重写后)在对象销毁之前被调用来释放所有分配的资源 |
MemberwiseClone() | 这个方法的作用是逐个成员地返回当前对象的副本,通常用于克隆对象 |
Overriding System.Object.ToString()
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 base. Once you have obtained your parent’s string data, you can append the derived class’s custom information.
重写System.Object.ToString()
如果重写ToString()让某个类来扩展自定义基类,第一件事情就是使用base从基类获取ToString()值。获取了父类字符串数据之后,就可以追加派生类的自定义信息。
Overriding System.Object.Equals()
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
public override bool Equals(object obj){ if (obj is Person && obj != null) { Person temp; temp = (Person)obj; if (temp.fName == this.fName && temp.lName == this.fName && temp.personAge == this.personAge) { return true; } else { return false; } } return false;}
重写System.Object.Equals()
在默认情况下,只有当被比较的两个对象引用内存中相同的对象实例时才会返回true。对于Person类,两个要比较的变量包含相同状态值时,实习Equals()返回true可能会很有用。
public override bool Equals(object obj){ if (obj is Person && obj != null) { Person temp; temp = (Person)obj; if (temp.fName == this.fName && temp.lName == this.fName && temp.personAge == this.personAge) { return true; } else { return false; } } return false;}
public override bool Equals(object obj){ // No need to cast 'obj' to a Person anymore, // as everything has a ToString() method. return obj.ToString() == this.ToString();}
如果类的ToString()方法正确实现,包括继承链中所有字段数据的话,就只需要比较对象的字符串数据即可:
public override bool Equals(object obj){ // 不需要将 'obj'强制转换为Person,因为他们都有 ToString() 方法. return obj.ToString() == this.ToString();}
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.
重写System.Object.GetHashCode()
如果一个类重写了Equals()方法,我们还需要重写默认的GetHashCode()实现。简单来说,散列码是表示对象某个状态的数字值。
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 the Social Security number), simply call GetHashCode() on that point of field data.
// Return a hash code based on the person's ToString() value.public override int GetHashCode(){ return this.ToString().GetHashCode();}
由于String类已经有完善的散列码算法来使用String的字符数据计算散列值,所以如果确信类的所有实例都会有不同字段数据,就只需要对这些字段数据点调用GetHashCode()。
// 根据person的ToString() 值返回散列码.public override int GetHashCode(){ return this.ToString().GetHashCode();}
static void SharedMembersOfObject(){ // Static members of System.Object. Person p3 = new Person("Sally", "Jones", 4); Person p4 = new Person("Sally", "Jones", 4); Console.WriteLine("P3 and P4 have same state: {0}", object.Equals(p3, p4)); Console.WriteLine("P3 and P4 are pointing to same object: {0}", object.ReferenceEquals(p3, p4));}
Here, you are able to simply send in two objects (of any type) and allow the System.Object class to determine the details automatically. These methods can be very helpful when you have redefined equality for a custom type, yet still need to quickly determine whether two reference variables point to the same location in memory (via the static ReferenceEquals()method).
System.Object的静态成员
static void SharedMembersOfObject(){ // System.Object的静态成员. Person p3 = new Person("Sally", "Jones", 4); Person p4 = new Person("Sally", "Jones", 4); Console.WriteLine("P3 and P4 have same state: {0}", object.Equals(p3, p4)); Console.WriteLine("P3 and P4 are pointing to same object: {0}", object.ReferenceEquals(p3, p4));}
在这里,我们只需要传入两个对象(任何类型)并允许System.Object类自动检测细节。如果为自定义类型重新定义了相等性,或者需要检测两个引用变量是否指向内存中相同区域(通过静态的ReferenceEquals()方法),这些方法就会很有用。