C#高级编程第11版

【1】4.2 继承的类型

1.C#不支持类的多继承,但它支持一个接口继承自多个接口。

2.单继承:单继承允许一个类继承自另外一个基类,C#支持。

3.多级继承:多级继承允许创建一个类继承自它的父类,而父类又继承自它的爷爷类,多级继承会是一个庞大的谱系结构。C#支持这一点。

4.接口继承:一个接口继承自另外一个接口,这里也允许一个接口继承自多个接口。

【2】4.2.2 结构和类

1.struct本身不支持继承,但它们可以支持接口的继承。

2.struct和class之间的区别:

  • struct总是派生自System.ValueType类,它可以实现任意数量的接口。
  • class则总是派生自System.Object类或者另外的某个类。它们也可以实现任意数量的接口。

【3】4.3 实现继承

1.继承于Object类的声明可以省略不写。

2.如果同时有父类和接口,类必须是第一个,写在所有接口之前。

3.如果一个类(或者一个结构)实现了接口,那么它继承的类和接口之间使用,进行分隔。

【4】4.3.1 虚方法

1.virtual修饰的方法为虚方法,暗示其子类最好有自己的实现。
2.override修饰的方法为重写方法,表示覆盖了基类原有方法的实现。

3.lambda操作符。

4.ToString方法作为Object类的成员。

5.当重写基类的方法时,方法签名(所有的参数类型以及方法名)和返回值必须完全匹配,如若不然,那么你创建的是一个新方法,而不是对基类方法的重写。

6.

1、当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚函数;

2、如果不是虚函数,那么它就直接执行该函数。而如果有virtual关键字,也就是一个虚函数,那么这个时候它就不会立刻执行该函数了,而是转去检查对象的实例类。

3、在这个实例类里,他会检查这个实例类的定义中是否有重新实现该虚函数(通过override关键字),如果是有,那么OK,它就不会再找了,而马上执行该实例类中的这个重新实现的函数。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重写了该虚函数的父类为止,然后执行该父类里重写后的函数。

7.virtual修饰的方法。其子类除了override,还可用new来修饰。

【5】4.3.2 多态性

假如Shape的Draw方法不是virtual修饰的虚方法或者Rectangle没有重写Draw方法,那么将Rectangle变量当做参数传递给DrawShape,执行的最终会是Shape.Draw方法,那么输出就会以"Shape With"开头。

如果有重写方法,那么按照重写的这个来。如果没有,则按照的你所定的方法来执行。

【6】4.3.3 隐藏方法

1.假如基类和子类都拥有一个相同签名的方法,并且它们没有分别用virtual和override进行声明, 那么子类版本将会隐藏基类版本的方法。

2.基类与派生类方法名相同,方法的实现不同。当派生类抛出一个编译警告时,可以用new来隐藏基类的成员。

3.如果不用new关键字,也可重命名方法名,或重写基类方法(假如存在跟你目的一致的虚方法的话)。而,万一你的方法有其他的外部引用的话,简单的修改方法名会导致代码出错。

4.new关键字不应该用来刻意隐藏基类的成员。它的主要目的只是为了解决版本冲突,当基类定义了某些派生类已经处理过的内容的时候。

【7】4.3.4 调用方法的基类版本

1.C#提供了一个特殊的语法以便子类可以调用父类的方法:base.<MethodName>。

base.Move(newPosition);

2.通过base关键字,你可以调用父类任意方法,并不单单只是你重写的那个方法。

【8】4.3.5 抽象类和抽象方法

1.C#允许将类和方法声明成abstract。一个抽象类无法被实例化,而一个抽象方法不能拥有任何方法体并且继承抽象类的非抽象子类必须实现此抽象方法。显而易见,抽象方法自动就是virtual方法(并且你不能再给虚方法加一个virtual修饰符,否则会得到一个编译错误)。任何含有抽象方法的类必须是一个抽象类。

2.抽象类无法被实例化,抽象类无方法体,无实现方法,都由子类去实现抽象类里所有抽象成员。

3.(子类)在方法体里直接抛出一个NotImplementationException异常来作为一种方法实现,但这往往只是开发过程中的一种临时手段而已,在该方法被正常调用的时候你仍然需要实现它。

【9】4.3.6 密封类和密封方法

1.假如你不想让你的类能被其他类继承,你可以将它声明成sealed,这样它就不能拥有任何子类了。而如果你将某个方法声明成sealed,意味着它不允许被重写。

2.使用密封类的理由:

  • 如果你需要将某个类或者方法标记为sealed,最可能的状况就是这个类或者方法,是用于某个类库、类又或者别的还在编辑的类,用于实现一些内部操作,任何对于这个类的重写都可能会导致代码的不稳定。举个例子,假如你从未测试过继承又或者还没决定继承的设计策略的话,将你的类声明成sealed不失为一个好方法。
  • 通过sealed类,编译器知道它肯定不会有派生类,因此不需要为它创建虚方法表,这点能提高性能。string类就是密封类。因为我从没见过任何一个成型的应用程序不使用string类的,因此这个类最好拥有更高的性能。将类声明成sealed对编译器来说是一个很好的暗示(good hint)。
  • 将方法声明成sealed的理由跟class差不多。某个方法可能是重写了基类的虚方法,假如将它声明成sealed方法,编译器就知道后续的类不可能再扩展此方法的虚方法表(vtable)了,就到此为止。

3.为了给方法或属性使用sealed修饰符,它们必须首先是override基类的virtual方法或者属性。假如你不想基类的方法或属性被重写,就别把它们声明成virtual方法或属性。

【10】4.3.7 派生类的构造函数

1.构造函数总是按照类的继承顺序依次调用的。System.Object的构造函数总是第一个,然后挨个调用,直到编译器遇到要实例化的那个类为止。为了创建Ellipse的实例,编译器先调用了Object构造函数,然后是Shape构造函数,最后才是Ellipse的构造函数。每个构造函数只处理它们自己字段的初始化。

2.base  命名参数。

【11】4.4.1 访问修饰符

1.public,protected,private是逻辑访问修饰符,而internal则是物理访问修饰符,它是按程序集来界定的。

2.你不能将包含在命名空间下的顶级类用protected,private,或者protected internal进行修饰,因为这些访问等级对它们毫无意义。因此,这些修饰符只能用在成员上。然而,你可以将这些修饰符定义在嵌套类型(就是一个类型里面再套一个其他类型)上,因为在这种情况下,内在的嵌套类型也可以当成一个成员来看,因此像下面这么写是正确的:

public class OuterClass
{
    protected class InnerClass
    {
        // ...
    }
    // ...
}

3.假如你定义了一个嵌套类型,内在类型永远能访问外在类型的所有成员。因此,在上面的例子里,InnerClass里的所有代码都可以访问OuterClass里的全体成员,即使那些成员是private声明的也不例外。

【12】4.4.2 其他修饰符

new,static,virtual,abstract,override,sealed,extern。

【13】4.5 接口

1.接口不允许提供任何成员的任何实现。总的来说,接口只能包含方法,属性,索引和事件的声明。

2.接口里的所有成员肯定是abstract的,接口不能有任何具体实现代码,它是纯粹的抽象。

3.接口既没有构造函数(不能实例化的对象有构造函数也没用啊),也没有字段(因为意味着需要一些内部实现)。接口也不许包含操作符重载。

4.接口内的成员不允许声明任何修饰符,接口成员总是隐式地声明为public,而且它们不能被virtual修饰。如何实现取决于继承接口的类。因此,由实现类来决定访问修饰符是最好的。

【14】4.5.1 定义和实现接口

1.接口名I。

2.实现接口和继承自某个父类是完全独立的,互不影响。

3.接口引用完全可以当成类引用来用,接口引用可以指向任何实现了这个接口的类。

【15】4.5.2 派生的接口

1.在方法的实现和调用过程中,你不需要知道你实际操作的对象具体是哪个类——你只需要知道那个对象只要实现了IBankAccount接口就行。

2.相当于一个接口继承另一个接口,这个接口比继承的接口多了一种办法。然后在子类中实现这个办法。

【16】4.6 is 和as 运算符

1.假如你有一个方法,接收一个Object类型的参数,但是你在方法体内想访问IBankAccount对象的成员时候,怎么办?

这个时候你就需要使用强制转换,将Object对象强制转换成IBankAccount类型,就像下面这样:

public void WorkWithManyDifferentObjects(object o)
{
    IBankAccount account = (IBankAccount)o; 
    // work with the account
}

传递过来的对象没有实现IBankAccount接口,这个时候你想进行强制转换,程序就会报错。

2.as运算符和强制转换操作符很类似,都是转换对象,返回一个引用。但是在转换失败时,as运算符不会抛出一个InvalidCastException异常,而是返回一个空引用,如下所示:

public void WorkWithManyDifferentObjects(object o)
{
    IBankAccount account = o as IBankAccount;
    if (account != null)
    {
        // work with the account
    }
}

这里在使用as运算符之前最好先判断一下参数o是否为null,如果对null值进行as转换会直接抛出一个NullReferenceException。

3.is运算符会根据某个对象是否是指定类型返回true和false。假如表达式返回的是true值,则将该对象转换成指定类型并赋值给随后的变量,就像下面这样:

public void WorkWithManyDifferentObjects(object o)
{
    if (o is IBankAccount account)
    {
        // work with the account
    }
}

is运算符最后追加变量声明是C# 7.0开始的新特性,这个特性属于模式匹配的一部分。

【17】

本章主要介绍了C#里面代码是如何进行继承的,详细讲解了C#是如何提供接口的多重继承和类的单继承,并且解释了C#是如何提供更丰富的语法设计使得构造继承代码更加的健壮易用。

这其中包括override关键字,它用来声明一个方法重写了基类的同名方法;也包括new关键字,它定义了子类隐藏了父类的同名方法;还包括严格的构造函数初始化规则,来确保构造函数之间以更稳健的方式进行交互。

参考网址:https://www.cnblogs.com/zenronphy/p/ProfessionalCSharp7Chapter4.html#41-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1

原文地址:https://www.cnblogs.com/wazz/p/14101788.html