1、类型构造器的执行所谓类型构造器也就是我们熟知的静态构造方法,在我们编写的类中,都会有一个默认的静态无参构造方法,跟无参实例构造方法一样是默认存在的。每当我们对一个类创建第一个实例或访问静态字段前,JIT 编译器就会调用该类的静态构造方法。当然,静态变量也可以使用上面说的内联方法进行赋值。这里可以看出,当第一次实例化某个类时,会首先调用该类的静态构造方法。C#中基类和子类实例化的顺序这个知识点比较简单,那就是在调用子类实例构造方法之前会调用基类的实例构造方法。从面试题的结果可以看出,基类的构造方法又比子类的静态构造函数晚一些,此处因个人能力有限,我也没办法从更底层的角度去分析原理,只能暂且记住吧
2、。new 修饰符的作用我看过不少关于 new 以修饰符的形式用在方法声明中的题目,关于 new 的用法在 MSDN 上也都查的到,官方说法是显式隐藏从基类继承的成员。我个人的理解比较简单:当子类中,一个方法的签名(指参数,方法名,返回值)与基类的一个方法相同,通过加入 new 修饰符,可以让子类不做更改的去使用该方法。说到底,new 修饰符就是让两个不相关的同名方法同时存在而已。(这里同名指相同的方法签名)静态类的主要特性: 仅包含静态成员。 无法实例化。 是密封的。 不能包含实例构造函数。 静态构造函数具有以下特点: 静态构造函数既没有访问修饰符,也没有参数。 在创建第一个实例或引用任何静态
3、成员之前,将自动调用静态构造函数来初始化类。 无法直接调用静态构造函数。 在程序中,用户无法控制何时执行静态构造函数。 静态构造函数的典型用途是:当类使用日志文件时,将使用这种构造函数向日志文件中写入项。 静态构造函数在为非托管代码创建包装类时也很有用,此时该构造函数可以调用 LoadLibrary 方法。 如果静态构造函数引发异常,运行时将不会再次调用该构造函数,并且在程序运行所在的应用程序域的生存期内,类型将保持未初始化。 结构与类共享大多数相同的语法,但结构比类受到的限制更多: 在结构声明中,除非字段被声明为 const 或 static,否则无法初始化。 结构不能声明默认构造函数(没有
4、参数的构造函数)或析构函数。 结构不能从类或其他结构继承。 结构在赋值时进行复制。将结构赋值给新变量时,将复制所有数据,并且对新副本所做的任何修改不会更改原始副本的数据。 结构是值类型,而类是引用类型。 与类不同,结构的实例化可以不使用 new 运算符。 结构可以声明带参数的构造函数。 一个结构不能从另一个结构或类继承,而且不能作为一个类的基。所有结构都直接继承自 System.ValueType,后者继承自 System.Object。 结构可以实现接口。 结构可用作可为 null 的类型,因而可向其赋 null 值。 抽象方法和虚方法 当基类将方法声明为 virtual 时,派生类可以用自
5、己的实现重写该方法。如果基类将成员声明为 abstract,则在直接继承自该类的任何非抽象类中都必须重写该方法。如果派生类自身是抽象的,则它继承抽象成员而不实现它们。抽象成员和虚成员是多态性的基础,多态性是面向对象的编程的第二个主要特性。有关更多信息,请参见 多态性(C# 编程指南)。 抽象基类 如果希望禁止通过 new 关键字直接进行实例化,可以将类声明为 abstract。如果这样做,则仅当从该类派生新类时才能使用该类。抽象类可以包含一个或多个自身声明为抽象的方法签名。这些签名指定参数和返回值,但没有实现(方法体)。抽象类不必包含抽象成员;但是,如果某个类确实包含抽象成员,则该类自身必须声
6、明为抽象类。自身不是抽象类的派生类必须为抽象基类中的任何抽象方法提供实现。有关更多信息,请参见抽象类、密封类及类成员(C# 编程指南)和抽象类设计。 接口 “接口” 是一种引用类型,有点像仅包含抽象成员的抽象基类。类在从接口派生时必须为该接口的所有成员提供实现。类虽然只能从一个直接基类派生,但可以实现多个接口。 接口用于为不一定具有“是”关系的类定义特定功能。例如,如果任何类或结构需要让客户端代码判断某类型的两个对象是否相等(无论该类型如何定义相等性),那么该类或结构就可以实现 IEquatable1 接口。IEquatable 不表示基类和派生类之间存在的同一种“ 是”关系(例如 Mamma
7、l 是 Animal)。有关更多信息,请参见 接口(C# 编程指南)。 派生类对基类成员的访问 派生类可以访问基类的公共成员、受保护成员、内部成员和受保护内部成员。即使派生类继承基类的私有成员,仍不能访问这些成员。但是,所有这些私有成员在派生类中仍然存在,且执行与基类自身中相同的工作。例如,假定一个受保护基类方法访问私有字段。要使继承的基类方法正常工作,派生类中必须有该字段。 禁止进一步派生 类可以将自身或其成员声明为 sealed,从而禁止其他类从该类自身或其任何成员继承。有关更多信息,请参见 抽象类、密封类及类成员(C# 编程指南)。 派生类隐藏基类成员 派生类可以通过以相同的名称和签名声
8、明基类成员来隐藏这些成员。可以使用 new 修饰符显式指示成员不作为基类成员的重写。不是必须要使用 new,但如果不使用 new,将生成编译器警告。有关更多信息,多态性常被视为自封装和继承之后,面向对象的编程的第三个支柱。Polymorphism(多态性)是一个希腊词,指“多种形态”,多态性具有两个截然不同的方面: 1. 在运行时,在方法参数和集合或数组等位置,派生类的对象可以作为基类的对象处理。发生此情况时,该对象的声明类型不再与运行时类型相同。 2. 基类可以定义并实现虚方法,派生类可以重写这些方法,即派生类提供自己的定义和实现。在运行时,客户端代码调用该方法,CLR 查找对象的运行时类型
9、,并调用虚方法的重写方法。因此,您可以在源代码中调用基类的方法,但执行该方法的派生类版本。 虚方法允许您以统一方式处理多组相关的对象。例如,假定您有一个绘图应用程序,允许用户在绘图图面上创建各种形状。您在编译时不知道用户将创建哪些特定类型的形状。但应用程序必须跟踪创建的所有类型的形状,并且必须更新这些形状以响应用户鼠标操作。您可以使用多态性通过两个基本步骤解决这一问题: 1. 创建一个类层次结构,其中每个特定形状类均派生自一个公共基类。 2. 使用虚方法通过对基类方法的单个调用来调用任何派生类上的相应方法。 首先,创建一个名为 Shape 的基类,并创建一些派生类,例如 Rectangle、C
10、ircle 和 Triangle。为 Shape 类提供一个名为 Draw 的虚方法,并在每个派生类中重写该方法以绘制该类表示的特定形状。创建一个 List 对象,并向该对象添加 Circle、Triangle 和 Rectangle。若要更新绘图图面,请使用 foreach 循环对该列表进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法。虽然列表中的每个对象都具有声明类型 Shape,但调用的将是运行时类型(该方法在每个派生类中的重写版本)。 在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 ObjectC# 语言经过专门设计,以便不同库中的基类与
11、派生类之间的版本控制可以不断向前发展,同时保持向后兼容。这具有多方面的意义。例如,这意味着在基类中引入与派生类中的某个成员具有相同名称的新成员在 C# 中是完全支持的,不会导致意外行为。它还意味着类必须显式声明某方法是要重写一个继承方法,还是一个隐藏具有类似名称的继承方法的新方法。 在 C# 中,派生类可以包含与基类方法同名的方法。 基类方法必须定义为 virtual。 如果派生类中的方法前面没有 new 或 override 关键字,则编译器将发出警告,该方法将有如存在 new 关键字一样执行操作。 如果派生类中的方法前面带有 new 关键字,则该方法被定义为独立于基类中的方法。 如果派生类
12、中的方法前面带有 override 关键字,则派生类的对象将调用该方法,而不是调用基类方法。 可以从派生类中使用 base 关键字调用基类方法。 override、virtual 和 new 关键字还可以用于属性、索引器和事件中。 默认情况下,C# 方法为非虚方法。如果某个方法被声明为虚方法,则继承该方法的任何类都可以实现它自己的版本。若要使方法成为虚方法,必须在基类的方法声明中使用 virtual 修饰符。然后,派生类可以使用 override 关键字重写基虚方法,或使用 new 关键字隐藏基类中的虚方法。如果 override 关键字和 new 关键字均未指定,编译器将发出警告,并且派生类
13、中的方法将隐藏基类中的方法。C#复制class GraphicsClasspublic virtual void DrawLine() public virtual void DrawPoint() 您的公司使用此类,并且您在添加新方法时将其用来派生自己的类: C#复制class YourDerivedGraphicsClass : GraphicsClasspublic void DrawRectangle() 您的应用程序运行正常,直到公司 A 发布了 GraphicsClass 的新版本,类似于下面的代码: C#复制class GraphicsClasspublic virtual vo
14、id DrawLine() public virtual void DrawPoint() public virtual void DrawRectangle() 现在,GraphicsClass 的新版本中包含一个名为 DrawRectangle 的方法。开始时,没有出现任何问题。新版本仍然与旧版本保持二进制兼容。已经部署的任何软件都将继续正常工作,即使新类已安装到这些软件所在的计算机系统上。在您的派生类中,对方法 DrawRectangle 的任何现有调用将继续引用您的版本。 但是,一旦您使用 GraphicsClass 的新版本重新编译应用程序,就会收到来自编译器的警告。有关更多信息,请
15、参见 编译器警告(等级 2)CS0108。 此警告提示您必须考虑希望 DrawRectangle 方法在应用程序中的工作方式。 如果您希望自己的方法重写新的基类方法,请使用 override 关键字: C#复制class YourDerivedGraphicsClass : GraphicsClasspublic override void DrawRectangle() override 关键字可确保派生自 YourDerivedGraphicsClass 的任何对象都将使用 DrawRectangle 的派生类版本。派生自 YourDerivedGraphicsClass 的对象仍可以使用
16、基关键字访问 DrawRectangle 的基类版本: C#复制base.DrawRectangle();如果您不希望自己的方法重写新的基类方法,则需要注意以下事项。为了避免这两个方法之间发生混淆,可以重命名您的方法。这可能很耗费时间且容易出错,而且在某些情况下并不可行。但是,如果您的项目相对较小,则可以使用 Visual Studio 的重构选项来重命名方法。有关更多信息,请参见重构类和类型。 或者,也可以通过在派生类定义中使用关键字 new 来防止出现该警告: C#复制class YourDerivedGraphicsClass : GraphicsClasspublic new void
17、 DrawRectangle() 使用 new 关键字可告诉编译器您的定义将隐藏基类中包含的定义。这是默认行为。 重写和方法选择当在类中指定方法时,如果有多个方法与调用兼容(例如,存在两种同名的方法,并且其参数与传递的参数兼容),则 C# 编译器将选择最佳方法进行调用。下面的方法将是兼容的: C#复制public class Derived : Basepublic override void DoWork(int param) public void DoWork(double param) 在 Derived 的一个实例中调用 DoWork 时,C# 编译器将首先尝试使该调用与最初在 De
18、rived 上声明的 DoWork 版本兼容。重写方法不被视为是在类上进行声明的,而是在基类上声明的方法的新实现。仅当 C# 编译器无法将方法调用与 Derived 上的原始方法匹配时,它才尝试将该调用与具有相同名称和兼容参数的重写方法匹配。例如: C#复制int val = 5;Derived d = new Derived();d.DoWork(val); / Calls DoWork(double).由于变量 val 可以隐式转换为 double 类型,因此 C# 编译器将调用 DoWork(double),而不是 DoWork(int)。有两种方法可以避免此情况。首先,避免将新方法声明
19、为与虚方法同名。其次,可以通过将 Derived 的实例强制转换为 Base 来使 C# 编译器搜索基类方法列表,从而使其调用虚方法。由于是虚方法,因此将调用 Derived 上的 DoWork(int) 的实现。例如: C#复制(Base)d).DoWork(val); / Calls DoWork(int) on Derived.使用 abstract 关键字可以创建必须在派生类中实现的不完整的类和类成员。 使用 sealed 关键字可以防止继承以前标记为 virtual 的类或某些类成员。 public同一程序集中的任何其他代码或引用该程序集的其他程序集都可以访问该类型或成员。 priv
20、ate只有同一类或结构中的代码可以访问该类型或成员。 protected只有同一类或结构或者派生类中的代码可以访问该类型或成员。 internal同一程序集中的任何代码都可以访问该类型或成员,但其他程序集中的代码不可以。 protected internal同一程序集中的任何代码或其他程序集中的任何派生类都可以访问该类型或成员。 6(共 6)对本文的评价是有帮助 - 评价此主题更新:2007 年 11 月委托和接口都允许类设计器分离类型声明和实现。任何类或结构都能继承和实现给定的接口。可以为任何类上的方法创建委托,前提是该方法符合委托的方法签名。接口引用或委托可由不了解实现该接口或委托方法的类
21、的对象使用。既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢? 在以下情况下,请使用委托: 当使用事件设计模式时。 当封装静态方法可取时。 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。 需要方便的组合。 当类可能需要该方法的多个实现时。 在以下情况下,请使用接口: 当存在一组可能被调用的相关方法时。 当类只需要方法的单个实现时。 当使用接口的类想要将该接口强制转换为其他接口或类类型时。 当正在实现的方法链接到类的类型或标识时:例如比较方法。 使用单一方法接口而不使用委托的一个很好的示例是 IComparable 或泛型版本 IComparable。IComparable 声明 CompareTo 方法,该方法返回一个整数,指定相同类型的两个对象之间的小于、等于或大于关系。IComparable 可用作排序算法的基础。虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的。