- 浏览: 1020940 次
- 性别:
- 来自: 天津
文章分类
- 全部博客 (445)
- Java (22)
- J2EE (18)
- Flex (102)
- Flex-blazeds (1)
- Flex-FABridge (2)
- Flex4 (3)
- CheckStyle (2)
- PowerDesigner (0)
- POI (2)
- Java2Word (2)
- 杂项 (15)
- 日记 (3)
- 数据库-oracle (41)
- 数据库-SQLServer (7)
- 中间件 (1)
- 英语 (8)
- C# (43)
- ASP.net (72)
- ASP.net MVC (28)
- 微软-Entity Framework (19)
- JavaScript (22)
- JQuery (31)
- EasyUI (3)
- VS2010 (4)
- CVS (1)
- Tomcat (3)
- Linux (1)
- 遗留问题 (1)
- iphone (1)
- MAC (0)
- 系统 (2)
- Web Service (4)
- Cache Manager (1)
- PMP (1)
- WCF (10)
- BootstrapJs (1)
- Web API (6)
- Go语言 (0)
- 网络协议 (2)
- Redis (10)
- RabbitMQ (10)
- Git (3)
- Kafka (5)
- ELK (5)
- Nginx (3)
- 测试 (2)
最新评论
-
ygm0720:
Table行拖拽自己实现 -
程乐平:
Flex4开发视频教程(27集)下载http://bbs.it ...
Flex4教程 -
liuweihug:
Jquery+asp.net 后台数据传到前台js进行解析的办 ...
AJAX $.toJSON的用法或把数组转换成json类型 -
weilikk:
谢谢谢谢!!
javascript IE下不能用 trim函数解决方法 -
gxz1989611:
vigiles 写道请问楼主:[Fault] exceptio ...
blazeds推送技术至Flex
一. 继承基础知识
为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在自己的开发过程中能够有
足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性--继承性inheritance
和多态性polymorphism。
继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。
现实世界中的许多实体之间不是相互孤立的,它们往往具有共同的特征也存在内在的差别。人们可以采用层次结构来描述这些实体之间的相似之处和不同之处。
上图反映了交通工具类的派生关系。最高层的实体往往具有最一般最普遍的特征,越下层的事物越具体,并且下层包含了上层的特征。它们之间的关系是基类与派生类之间的关系。
为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。
注意:C#中,派生类只能从一个类中继承。这是因为,在C++中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中派生一个类这往往会带来许多问题,从而抵消了这种灵活性带来的优势。
C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。看下面示例:
using System ; class Vehicle //定义交通工具(汽车)类 { protected int wheels ; //公有成员:轮子个数 protected float weight ; //保护成员:重量 public Vehicle( ){;} public Vehicle(int w,float g){ wheels = w ; weight = g ; } public void Speak( ){ Console.WriteLine( "交通工具的轮子个数是可以变化的! " ) ; } } ; class Car:Vehicle //定义轿车类:从汽车类中继承 { int passengers ; //私有成员:乘客数 public Car(int w , float g , int p) : base(w, g) { wheels = w ; weight = g ; passengers=p ; } } |
Vehicle 作为基类,体现了"汽车"这个实体具有的公共性质:汽车都有轮子和重量。Car 类继承了Vehicle 的这些性质,并且添加了自身的特性:可以搭载乘客。
二、C#中的继承符合下列规则:
1、继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。
2、派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。
3、构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。
4、派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。
5、类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。
6、派生类只能从一个类中继承,可以通过接吕实现多重继承。
下面的代码是一个子类继承父类的例子:
using System ; public class ParentClass { public ParentClass( ) { Console.WriteLine("父类构造函数。"); } public void print( ) { Console.WriteLine("I'm a Parent Class。") ; } } public class ChildClass : ParentClass { public ChildClass( ) { Console.WriteLine("子类构造函数。") ; } public static void Main( ) { ChildClass child = new ChildClass( ) ; child.print( ) ; } } |
程序运行输出:
父类构造函数。子类构造函数。I'm a Parent Class。
上面的一个类名为ParentClass, main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类ChildClass。
1.首先必须说明ParentClass是ChildClass的基类。
这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass : ParentClass"。在派生类标识符后面,用分号":" 来表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。
2.ChildClass的功能几乎等同于ParentClass。
因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main( )方法中,调用print( )
方法的结果,就验证这一点。该子类并没有自己的print( )方法,它使用了ParentClass中的 print(
)方法。在输出结果中的第三行可以得到验证。
3.基类在派生类初始化之前自动进行初始化。ParentClass 类的构造函数在ChildClass的构造函数之前执行。
三. 访问与隐藏基类成员
(1) 访问基类成员
通过base 关键字访问基类的成员:
调用基类上已被其他方法重写的方法。
指定创建派生类实例时应调用的基类构造函数。
基类访问只能在构造函数、实例方法或实例属性访问器中进行。
从静态方法中使用 base 关键字是错误的。
示例:下面程序中基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base 关键字,可以从派生类中调用基类上的 Getinfo 方法。
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; public class ParentClass { public ParentClass( ) { Console.WriteLine("父类构造函数。"); } public void print( ) { Console.WriteLine("I'm a Parent Class。") ; } } public class ChildClass : ParentClass { public ChildClass( ) { Console.WriteLine("子类构造函数。") ; } public static void Main( ) { ChildClass child = new ChildClass( ) ; child.print( ) ; } } |
程序运行输出:
父类构造函数。子类构造函数。I'm a Parent Class。
上面的一个类名为ParentClass, main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类ChildClass。
1.首先必须说明ParentClass是ChildClass的基类。
这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass : ParentClass"。在派生类标识符后面,用分号":" 来表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。
2.ChildClass的功能几乎等同于ParentClass。
因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main( )方法中,调用print( )
方法的结果,就验证这一点。该子类并没有自己的print( )方法,它使用了ParentClass中的 print(
)方法。在输出结果中的第三行可以得到验证。
3.基类在派生类初始化之前自动进行初始化。ParentClass 类的构造函数在ChildClass的构造函数之前执行。
三. 访问与隐藏基类成员
(1) 访问基类成员
通过base 关键字访问基类的成员:
调用基类上已被其他方法重写的方法。
指定创建派生类实例时应调用的基类构造函数。
基类访问只能在构造函数、实例方法或实例属性访问器中进行。
从静态方法中使用 base 关键字是错误的。
示例:下面程序中基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base 关键字,可以从派生类中调用基类上的 Getinfo 方法。
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; class Vehicle //定义交通工具(汽车)类 { protected int wheels ; //公有成员:轮子个数 protected float weight ; //保护成员:重量 public Vehicle( ){;} public Vehicle(int w,float g){ wheels = w ; weight = g ; } public void Speak( ){ Console.WriteLine( "交通工具的轮子个数是可以变化的! " ) ; } } ; class Car:Vehicle //定义轿车类:从汽车类中继承 { int passengers ; //私有成员:乘客数 public Car(int w , float g , int p) : base(w, g) { wheels = w ; weight = g ; passengers=p ; } } |
Vehicle 作为基类,体现了"汽车"这个实体具有的公共性质:汽车都有轮子和重量。Car 类继承了Vehicle 的这些性质,并且添加了自身的特性:可以搭载乘客。
二、C#中的继承符合下列规则:
1、继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。
2、派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。
3、构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。
4、派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。
5、类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。
6、派生类只能从一个类中继承,可以通过接吕实现多重继承。
下面的代码是一个子类继承父类的例子:
using System ; public class ParentClass { public ParentClass( ) { Console.WriteLine("父类构造函数。"); } public void print( ) { Console.WriteLine("I'm a Parent Class。") ; } } public class ChildClass : ParentClass { public ChildClass( ) { Console.WriteLine("子类构造函数。") ; } public static void Main( ) { ChildClass child = new ChildClass( ) ; child.print( ) ; } } |
程序运行输出:
父类构造函数。子类构造函数。I'm a Parent Class。
上面的一个类名为ParentClass, main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类ChildClass。
1.首先必须说明ParentClass是ChildClass的基类。
这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass : ParentClass"。在派生类标识符后面,用分号":" 来表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。
2.ChildClass的功能几乎等同于ParentClass。
因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main( )方法中,调用print( )
方法的结果,就验证这一点。该子类并没有自己的print( )方法,它使用了ParentClass中的 print(
)方法。在输出结果中的第三行可以得到验证。
3.基类在派生类初始化之前自动进行初始化。ParentClass 类的构造函数在ChildClass的构造函数之前执行。
三. 访问与隐藏基类成员
(1) 访问基类成员
通过base 关键字访问基类的成员:
调用基类上已被其他方法重写的方法。
指定创建派生类实例时应调用的基类构造函数。
基类访问只能在构造函数、实例方法或实例属性访问器中进行。
从静态方法中使用 base 关键字是错误的。
示例:下面程序中基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base 关键字,可以从派生类中调用基类上的 Getinfo 方法。
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; public class ParentClass { public ParentClass( ) { Console.WriteLine("父类构造函数。"); } public void print( ) { Console.WriteLine("I'm a Parent Class。") ; } } public class ChildClass : ParentClass { public ChildClass( ) { Console.WriteLine("子类构造函数。") ; } public static void Main( ) { ChildClass child = new ChildClass( ) ; child.print( ) ; } } |
程序运行输出:
父类构造函数。子类构造函数。I'm a Parent Class。
上面的一个类名为ParentClass, main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类ChildClass。
1.首先必须说明ParentClass是ChildClass的基类。
这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass : ParentClass"。在派生类标识符后面,用分号":" 来表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。
2.ChildClass的功能几乎等同于ParentClass。
因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main( )方法中,调用print( )
方法的结果,就验证这一点。该子类并没有自己的print( )方法,它使用了ParentClass中的 print(
)方法。在输出结果中的第三行可以得到验证。
3.基类在派生类初始化之前自动进行初始化。ParentClass 类的构造函数在ChildClass的构造函数之前执行。
三. 访问与隐藏基类成员
(1) 访问基类成员
通过base 关键字访问基类的成员:
调用基类上已被其他方法重写的方法。
指定创建派生类实例时应调用的基类构造函数。
基类访问只能在构造函数、实例方法或实例属性访问器中进行。
从静态方法中使用 base 关键字是错误的。
示例:下面程序中基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base 关键字,可以从派生类中调用基类上的 Getinfo 方法。
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; public class Person { protected string ssn = "111-222-333-444" ; protected string name = "张三" ; public virtual void GetInfo() { Console.WriteLine("姓名: {0}", name) ; Console.WriteLine("编号: {0}", ssn) ; } } class Employee: Person { public string id = "ABC567EFG23267" ; public override void GetInfo() { // 调用基类的GetInfo方法: base.GetInfo(); Console.WriteLine("成员ID: {0}", id) ; } } class TestClass { public static void Main() { Employee E = new Employee() ; E.GetInfo() ; } } |
程序运行输出:
姓名: 张三
编号: 111-222-333-444
成员ID: ABC567EFG23267
示例:派生类同基类进行通信。
using System ; public class Parent { string parentString; public Parent( ) { Console.WriteLine("Parent Constructor.") ; } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString) ; } public void print( ) { Console.WriteLine("I'm a Parent Class.") ; } } public class Child : Parent { public Child( ) : base("From Derived") { Console.WriteLine("Child Constructor.") ; } public void print( ) { base.print( ) ; Console.WriteLine("I'm a Child Class.") ; } public static void Main( ) { Child child = new Child( ) ; child.print( ) ; ((Parent)child).print( ) ; } } |
程序运行输出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
说明:
1.派生类在初始化的过程中可以同基类进行通信。
上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。
2.有时,对于基类已有定义的方法,打算重新定义自己的实现。
Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。
3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。
方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的执行结果出现上面的第三行和第四行。
4.访问基类成员的另外一种方法是:通过显式类型转换。
在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。
2) 隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分
困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个密封类(sealed
class)的概念,帮助开发人员来解决这一问题。
密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
让我们看下面的例子:
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
bstract class A { public abstract void F( ) ; } sealed class B: A { public override void F( ) { // F 的具体实现代码 } } |
如果我们尝试写下面的代码
class C: B{ }
C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。
(3) 密封方法
我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。
不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:
using System ; class A { public virtual void F( ) { Console.WriteLine("A.F") ; } public virtual void G( ) { Console.WriteLine("A.G") ; } } class B: A { sealed override public void F( ) { Console.WriteLine("B.F") ; } override public void G( ) { Console.WriteLine("B.G") ; } } class C: B { override public void G( ) { Console.WriteLine("C.G") ; } } |
类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的派生类C 中,可以重载方法G,但不能重载方法F。
(4) 使用 new 修饰符隐藏基类成员
使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
public class MyBase { public int x ; public void MyVoke() ; } |
在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:
public class MyDerived : MyBase { new public void MyVoke (); } |
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的声明中使用 new 修饰符将发出警告。
示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
using System ; public class MyBase { public static int x = 55 ; public static int y = 22 ; } public class MyDerived : MyBase { new public static int x = 100; // 利用new 隐藏基类的x public static void Main() { // 打印x: Console.WriteLine(x); //访问隐藏基类的 x: Console.WriteLine(MyBase.x); //打印不隐藏的y: Console.WriteLine(y); } } |
输出: 100 55 22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
四、多级继承
一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。
只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中类的继承只可以是
一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接口的多重继承进行介绍:
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
using System ; //定义一个描述点的接口 interface IPoint { int x { get ; set ; } int y { get ; set ; } } interface IPoint2 { int y { get ; set ; } } //在point中继承了两个父类接口,并分别使用了两个父类接口的方法 class Point:IPoint,IPoint2 { //定义两个类内部访问的私有成员变量 private int pX ; private int pY ; public Point(int x,int y) { pX=x ; pY=y ; } //定义的属性,IPoint接口方法实现 public int x { get { return pX ; } set { pX =value ; } } //IPoint1接口方法实现 public int y { get { return pY ; } set { pY =value ; } } } class Test { private static void OutPut( IPoint p ) { Console.WriteLine("x={0},y={1}",p.x,p.y) ; } public static void Main( ) { Point p =new Point(15,30) ; Console.Write("The New Point is:") ; OutPut( p ) ; string myName =Console.ReadLine( ) ; Console.Write("my name is {0}", myName) ; } } |
五、继承与访问修饰符 1、继承中关于可访问域的一些问题 类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card
是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People
必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。 下表汇总了对使用声明的可访问性级别的限制。 示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。 using System ;
访问修饰符是一些关键字,用于指定声明的成员或类型的可访问性。类的继承中有四个访问修饰符: public protected internal
private。使用这些访问修饰符可指定下列五个可访问性级别: public protected internal internal
protected private。
声明的可访问性
意义
public
访问不受限制。
protected
访问仅限于包含类或从包含类派生的类型。
internal
访问仅限于当前项目。
protected internal
访问仅限于从包含类派生的当前项目或类型。
private
访问仅限于包含类型。
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A
{
int x ;
static void F(B b) {
b.x = 1 ; // 对
}
}
class B: A
{
static void F(B b) {
b.x = 1 ; // 错误
}
}
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ;
public enum sex
{ woman, man, } ;
abstract public class People
{
private string s_name;
public virtual string Name
{
get
{ return s_name ; }
}
private sex m_sex ;
public virtual sex Sex
{
get
{ return m_sex ; }
protected string s_card;
public abstract string Card
{ get; set; }
}
class Customer: People
{
string s_no ;
int i_day ;
public string No
{
get
{ return s_no ; }
set
{
if (s_no != value)
s_no = value;
}
}
public int Day
{
get
{ return i_day ; }
set
{
if (i_day != value)
i_day = value;
}
}
public override string Name
{
get { return base.Name; }
}
public override sex Sex
{
get { return base.Sex }
}
public override string Card
{
get
{ return s_ card ; }
set
{ s_ card = value ; }
}
}
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...}
public class MyClass: BaseClass {...} // Error
上下文
备注
类
类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口
接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托
委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数
常数的类型必须至少与常数本身具有同样的可访问性。
字段
字段的类型必须与至少字段本身具有同样的可访问性。
方法
方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性
属性的类型必须至少与属性本身具有同样的可访问性。
事件
事件的类型必须至少与事件本身具有同样的可访问性。
索引器
索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符
运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数
构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
声明的可访问性 | 意义 |
public | 访问不受限制。 |
protected | 访问仅限于包含类或从包含类派生的类型。 |
internal | 访问仅限于当前项目。 |
protected internal | 访问仅限于从包含类派生的当前项目或类型。 |
private | 访问仅限于包含类型。 |
1、继承中关于可访问域的一些问题
基类的所有成员(实例构造函数、析构函数和静态构造函数除外)都由派生类型继承。这甚至包括基类的私有成员。但是,私有成员的可访问域只包括声明该成员的类型的程序文本。在下面的示例中
class A { int x ; static void F(B b) { b.x = 1 ; // 对 } } class B: A { static void F(B b) { b.x = 1 ; // 错误 } } |
类 B 继承类 A 的私有成员 x。因为该成员是私有的,所以只能在 A 的"类体"中对它进行访问。因此,对 b.x 的访问在 A.F 方法中取得了成功,在 B.F 方法中却失败了。
2、继承中关于属性的一些问题
和类的成员方法一样,我们也可以定义属性的重载、虚属性、抽象属性以及密封属性的概念。与类和方法一样,属性的修饰也应符合下列规则:
属性的重载
1. 在派生类中使用修饰符的属性,表示对基类中的同名属性进行重载。
2. 在重载的声明中,属性的名称、类型、访问修饰符都应该与基类中被继承的属性一致。
3. 如果基类的属性只有一个属性访问器,重载后的属性也应只有一个。但如果基类的属性同时包含get 和set 属性访问器,重载后的属性可以只有一个,也可以同时有两个属性访问器。
注意:与方法重载不同的是,属性的重载声明实际上并没有声明新的属性,而只是为已有的虚属性提供访问器的具体实现。
虚属性
1. 使用virtual 修饰符声明的属性为虚属性。
2. 虚属性的访问器包括get 访问器和set 访问器,同样也是虚的。
抽象属性
1. 使用abstract 修饰符声明的属性为抽象属性。
2. 抽象属性的访问器也是虚的,而且没有提供访问器的具体实现。这就要求在非虚的派生类中,由派生类自己通过重载属性来提供对访问器的具体实现。
3. abstract 和override 修饰符的同时使用,不但表示属性是抽象的,。而且它重载了基类中的虚属性这时属性的访问器也是抽象的。
4. 抽象属性只允许在抽象类中声明。
5. 除了同时使用abstract 和override 修饰符这种情况之外,static, virtual, override和abstract 修饰符中任意两个不能再同时出现。
密封属性
1. 使用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。
2. 属性声明时如果有sealed 修饰符,同时也必须要有override 修饰符。
从上面可以看出,属性的这些规则与方法十分类似。对于属性的访问器,我们可以把get
访问器看成是一个与属性修饰符相同、没有参数、返回值为属性的值类型的方法,把set 访问器看成是一个与属性修饰符相同、仅含有一个value
参数、返回类型为void 的方法。看下面的程序:
using System ; public enum sex { woman, man, } ; abstract public class People { private string s_name; public virtual string Name { get { return s_name ; } } private sex m_sex ; public virtual sex Sex { get { return m_sex ; } protected string s_card; public abstract string Card { get; set; } } |
上面的例子中声明了"人"这个类,人的姓名Name 和性别Sex 是两个只读的虚属性:身份证号Card 是一个抽象属性,允许读写,因为类People 中包含了抽象属性Card,所以People 必须声明是抽象的。下面我们为住宿的客人编写一个类类从People 中继承。再看下面的程序:
class Customer: People { string s_no ; int i_day ; public string No { get { return s_no ; } set { if (s_no != value) s_no = value; } } public int Day { get { return i_day ; } set { if (i_day != value) i_day = value; } } public override string Name { get { return base.Name; } } public override sex Sex { get { return base.Sex } } public override string Card { get { return s_ card ; } set { s_ card = value ; } } } |
在类Customer 中,属性Name、 Sex 和Card 的声明都加上了override 修饰符,属性的声明都与基类People
中保持一致。Name 和Sex 的get 访问器,Card 的get 和set访问器都使用了base 关键字来访问基类People
中的访问器属性。Card 的声明重载了基类People 中的抽象访问器。这样,在Customer
类中没有抽象成员的存在,Customer可以是非虚的。
3、继承中对使用可访问性级别的限制
声明类型时,最重要的是查看该类型是否必须"至少"与其他成员或类型"具有同样的可访问性"。例如,直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误,因为基类 BaseClass 的可访问性小于 MyClass:
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ; |
class BaseClass {...} public class MyClass: BaseClass {...} // Error |
下表汇总了对使用声明的可访问性级别的限制。
上下文 | 备注 |
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。 |
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。 |
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。 |
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。 |
字段 | 字段的类型必须与至少字段本身具有同样的可访问性。 |
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。 |
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。 |
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。 |
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。 |
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。 |
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。 |
示例:以下示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。
using System ;
delegate int MyDelegate( ) ;
class B
{ // 定义一个私有的函数:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定义:
public B myField = new B();// 错误: 类型B与A字段A.myField级别不同
// 构造函数:
public readonly B myConst = new B(); //错误: 类型B是仅读的
//方法:
public B MyMethod()
{
return new B();
}
//属性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
发表评论
-
DotNet 资源大全(下)
2018-07-12 10:18 580HTML 和 CSS(HTML and CSS) ... -
DotNet 资源大全(上)
2018-07-12 10:17 727API 框架 NancyF ... -
C#Make自动化构建-简介
2018-05-25 17:10 699. Cake是什么? Cake是C# ... -
从零开始构建MSBuild C#项目文件
2018-05-25 16:08 717准备条件 一个好用的文本编辑器,例如Atom或者Subl ... -
asp.net mvc处理css和js版本问题
2018-05-24 16:40 1391当服务的修改了js和css内容后,发布到II ... -
分布式系统调用链监控
2018-02-22 14:36 860分布式系统调用链监 ... -
TOKEN+签名验证
2018-02-01 13:24 649首先问大家一个问题 ... -
C# LINQ to SQL
2017-07-28 12:48 6751、Concat(连接不同的集合不会自动过滤相同项。会延迟计 ... -
MongodDB client connect Server with Replication set and auth
2017-07-03 16:50 493写在这里,留作记录: 1. 利用字符串 ... -
IIS 优化
2017-06-28 21:57 1667通过对IIS7的配置进行优化,调整IIS7应用池的队列长度, ... -
IIS初始化(预加载),解决第一次访问慢,程序池被回收问题
2017-06-28 21:39 2070读在最前面: 1、本文以IIS8,Windows Se ... -
ASP.NET WebAPI HTTPS
2017-03-08 15:39 1655参照文档 http://southwo ... -
基于 EntityFramework 的数据库主从读写分离服务插件
2017-03-08 15:06 11701. 版本信息和源码 1.1 版本信息 v1.01 be ... -
SQL2K,DTC错误:"该伙伴事务管理器已经禁止了它对远程/网络事务的支持"的解决办法
2017-01-05 10:39 1083近日在用wf做数据持久化时发现:该伙伴事务管理器已经禁止了它 ... -
夸数据库业务事务处理-配置msdtc
2017-01-05 10:37 694配置msdtc需要分别配置数据库服务器和开发机,因为只有两个 ... -
C#中跨数据库增删改的事务控制
2017-01-04 13:25 684在程序的开发过程中,对数据库的增删改通常要用到事务,用来实现 ... -
.Net下RabbitMQ的使用(9) -- 在WCF下使用RabbitMQ
2016-12-19 10:42 1010RabbitMQ .net客户端通过自定义的Binding ... -
.Net下RabbitMQ的使用(8) -- 远程过程调用RPC
2016-12-19 10:42 1056RPC是在计算中是一种 ... -
.Net下RabbitMQ的使用(7) -- 消息的传输控制
2016-12-19 10:41 974前文中也多次提到消息传输的一些概念,这一篇比较全面的介绍一 ... -
.Net下RabbitMQ的使用(6) -- 持久化
2016-12-19 10:41 747消息的持久化是消息 ...
相关推荐
**深入剖析C#继承机制** 在C#编程语言中,继承是面向对象编程的一个核心特性,它允许一个类(称为子类或派生类)从另一个类(称为基类或父类)继承属性和方法。这使得代码复用变得更加简单,并且能够构建层次化的类...
【C#继承机制详解】 C#作为一门面向对象的编程语言,其继承性(Inheritance)是核心特性之一,旨在促进代码的复用和扩展,提高软件开发的效率。继承允许一个类(派生类)从另一个类(基类)获取特征和功能,同时还...
在`案例3:通过实例来剖析C#继承机制`中,你可能将看到如何创建一个实际的继承链,如何调用父类和子类的方法,以及如何利用继承实现特定的业务逻辑。通过这种方式,你可以更深入地理解C#继承的实际应用和优势。 总...
本实例将深入剖析C#中的继承机制,帮助你更好地理解和应用这一概念。 首先,继承的基本语法是使用冒号(:)来表示子类对基类的继承。例如: ```csharp public class BaseClass { // 基类成员 } public class ...
总的来说,这个资源提供了对C#继承机制的实践操作,包括基本的继承、多态、接口、抽象类以及密封类的应用。通过这些实例,学习者可以更好地理解如何在实际项目中利用继承提高代码的可维护性和复用性。记得动手实践,...
实践中,尝试理解和修改这些代码,将有助于巩固你的理论知识,并加深对C#继承机制的理解。在实际开发中,继承是构建复杂系统、模块化代码的关键工具,熟练掌握这一特性对于任何C#开发者来说都是至关重要的。
总之,C++的继承机制提供了更多的灵活性,包括多重继承,而C#的单一继承和更严格的封装规则使得代码更加简洁和易于管理。两种语言的继承机制在实际应用中各有优势,选择哪种取决于具体项目的需求和团队的技术背景。
标题和描述中的“C#继承测试”表明我们将探讨如何在C#中使用继承进行功能的组织和测试。 1. **继承的概念**:继承是对象之间的层次关系,子类可以继承父类的所有非私有成员(包括字段、方法、属性等)。这使得子类...
案例1:循环语言的学习-砝码程序验证 案例2:随机数的案例-洗牌程序 案例3:通过实例来剖析C#继承机制 案例4:递归与全局变量的案例1-打靶程序 案例5:递归与全局变量的案例2-二叉树 案例6:引用类型...
在这个“C#继承知识点”中,我们将深入探讨这一关键特性。 首先,我们要理解继承的基本语法。在C#中,子类通过使用冒号":"来声明其父类。例如,如果我们有一个名为`Animal`的父类,我们可以创建一个名为`Dog`的子类...
实例3 通过实例来剖析C#继承机制 实例4 递归与全局变量的案例1-打靶程序 实例5 递归与全局变量的案例2-二叉树 实例6 引用类型的举例---消去字符串空格 实例7 委托的使用方法实例---加减大小比较 实例8 用C#...
C#继承和多态的应用
在面向对象编程中,**继承**是一种重要的机制,它允许创建一个新类(称为派生类或子类),该类继承了现有类(称为基类或父类)的属性和行为。这种特性有助于代码的复用,并且能够构建出更加灵活和易于维护的程序结构...
本文将详细解析一个具体的案例:“C#继承与重写实现工资的预算”,通过分析提供的代码片段,我们将深入了解如何利用继承和重写来构建灵活的工资预算系统。 ### 继承的概念 继承是一种机制,允许创建一个新的类...
总的来说,C# 中的继承机制是面向对象编程的核心部分,它帮助我们构建更加灵活、可扩展和易于维护的代码。理解并熟练掌握继承的原理和实践,对于提升C#编程技能至关重要。通过实际的项目练习和不断地学习,初学者...
在"案例3:通过实例来剖析C#继承机制"中,你可能会学习到以下关键点: 1. **访问修饰符**:理解`public`, `private`, `protected` 和 `internal`等访问修饰符如何影响继承中的成员可见性。 2. **虚方法与重写**:...
在"C#继承的应用.ppt"中,主要探讨了C#中继承的使用,适合初学者学习。本节将深入讲解C#的继承机制。 首先,C#支持两种类型的继承:实现继承和接口继承。实现继承意味着一个类从另一个基类继承,获取基类的所有成员...