接口和抽象类:Interface、abstract
一、接口
接口是C#中很常见的工具,概念什么的就不说了,这里讲几个值得注意的小地方:
1、接口内部只能有函数、属性和事件的声明:
{
void Show();
string Type
{
get;
set;
}
event AddChildren Add;
}
在接口中声明的成员都不需要访问修饰符(public,private等),因为接口成员的权限默认都是public,另外值得注意的是接口中之所以能够声明事件是因为事件就是委托的特殊属性。
接口不能是静态的,如下声明就是错误的:
{
static void Show();
string Type
{
get;
set;
}
event AddChildren Add;
}
你会得到提示:
错误 1 修饰符“static”对该项无效 C:\Documents and Settings\HMD\桌面\CLR_Excise\CLR_Excise\Lib.cs 10 22 CLR_Excise
原因是如果接口是静态的就无法被继承。虽然接口不能是静态的,但是从C# 8.0开始,接口中可以定义静态成员了,并且接口也可以定义成员的默认实现,详情参考:
2、接口支持部分声明:
上面的接口实际上可以在一个命名空间里声明为3部分:
{
void Show();
}
partial interface IParent
{
string Type
{
get;
set;
}
}
partial interface IParent
{
event AddChildren Add;
}
上面这种部分接口的声明方式和声明为一个接口的效果完全相同,若要了解部分关键字partial请查看:C#里partial关键字的作用(转摘)
3、接口的实现:
接口成员的实现方式分为两种:
<1>隐式实现:
{
#region IParent 成员
public void Show()
{
throw new NotImplementedException();
}
public string Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public event AddChildren Add;
#endregion
}
在类中隐式声明的接口成员都必须是public访问权限
(2)如果有一个类ChildrenA实现了两个接口IParent、IFather,且IParent、IFather都有同名函数void Show();那么可以在ChildrenA中隐式实现Show,这样即实现了IParent的Show也实现了IFather的Show
例如:
public interface IParent
{
void Show();
string Type
{
get;
set;
}
event AddChildren Add;
}
public interface IFather
{
void Show();
string Type
{
get;
set;
}
event AddChildren Add;
}
public class ChildrenA : IParent, IFather
{
#region IParent,IFather 成员
public void Show()
{
throw new NotImplementedException();
}
public string Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public event AddChildren Add;
#endregion
}
在上面类ChildrenA中将IParent和IFather的同名成员都用隐式实现接口成员的方式一次性实现了。
(3)隐式实现接口属性的时候可以扩充接口属性的访问器,例如:
{
string Type
{
get;
}
}
public class CParent : IParent
{
#region IParent 成员
public string Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
#endregion
}
本来接口属性Type只有get访问器,但是实现接口的类CParent扩充了接口属性Type即定义了get访问器也定义了set访问器,这在隐式实现接口属性的时候是允许的,但是在显式实现接口属性的时候必须严格按照接口定义的格式来!
<2>显式声明接口:
{
void IParent.Show()
{
throw new NotImplementedException();
}
string IParent.Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
event AddChildren IParent.Add
{
add { throw new NotImplementedException(); }
remove { throw new NotImplementedException(); }
}
}
显式实现接口不能设置实现成员的访问权限(例如public、protected、private等)
此外显式实现接口需要注意以下几点:
(1)显式实现接口,那么实现的接口成员只能由接口调用,因为显式实现的接口成员对于实现类来说是不可见的。
(2)显式实现接口事件的时候要注意,只能显示实现事件的定义,例如显式实现IParent.Add事件的时候,用的显式实现事件:
event AddChildren IParent.Add
{
add { throw new NotImplementedException(); }
remove { throw new NotImplementedException(); }
}
如果用隐式实现事件就会报错:
event AddChildren IParent.Add
提示:错误 1 事件的显式接口实现必须使用事件访问器语法 C:\Documents and Settings\HMD\桌面\CLR_Excise\CLR_Excise\Lib.cs 45 38 CLR_Excise
(3)显式实现接口的成员可以通过this关键字访问实现类的成员,而实现类的成员可以通过(this as <interface>)访问显式实现接口的成员:
interface IParent { void Show(); /// <summary> /// 显式实现接口的成员调用实现类的成员 /// </summary> void Call(); } class CParent : IParent { #region IParent 成员 void IParent.Show() { Console.WriteLine("IParent.Show() called"); } /// <summary> /// 显式实现接口的成员调用实现类的成员 /// </summary> void IParent.Call() { this.Show(); } #endregion #region CParent 成员 public void Show() { Console.WriteLine("CParent.Show() called"); } /// <summary> /// 实现类的成员调用显式实现接口的成员 /// </summary> public void Call() { (this as IParent).Show(); } #endregion } class Program { static void Main(string[] args) { CParent cParent = new CParent(); IParent iParent = cParent; cParent.Call(); iParent.Call(); Console.WriteLine("Press any key to end..."); Console.ReadKey(); } }
上面代码中,我们让实现类CParent的成员Call调用了显式实现接口的成员IParent.Show,又用显式实现接口的成员IParent.Call调用了实现类CParent的成员Show,执行结果如下:
IParent.Show() called CParent.Show() called Press any key to end...
(4)上面隐式实现接口(2)中的例子没有区分IParent、IFather同名成员的实现,而是在继承类ChildrenA中由隐式实现方式统一实现。下面举例说明如何将IParent、IFather的同名成员分别实现:
还是有一个类ChildrenA实现了两个接口IParent、IFather,且IParent、IFather都有同名函数void Show();,这次为了区分IParent、IFather同名成员Show函数的实现,可以在ChildrenA中将一个接口的Show函数用显式方式实现,这说明在实现接口的时候可以将其中一个接口的同名成员用显式方式实现,以区分同名成员的实现
interface IParent
{
void Show();
string Type
{
get;
set;
}
event AddChildren Add;
}
interface IFather
{
void Show();
string Type
{
get;
set;
}
event AddChildren Add;
}
class ChildrenA : IParent, IFather
{
#region IParent 成员
public void Show()
{
throw new NotImplementedException();
}
public string Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public event AddChildren Add;
#endregion
#region IFather 成员
void IFather.Show()
{
throw new NotImplementedException();
}
string IFather.Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
event AddChildren IFather.Add
{
add { throw new NotImplementedException(); }
remove { throw new NotImplementedException(); }
}
#endregion
}
现在就将IParent和IFather接口的同名成员分开实现了,其中将IFather接口的同名成员用显式方式声明以区分IParent接口的实现。事实上可以将IParent和IFather的成员都以显式方式实现,不过这样实现类ChildrenA就不能访问到IParent和IFather的成员了,只有用IParent和IFather来访问它们各自的成员。
(5)如果实现类继承了具有同名成员的抽象类,那么加上override关键字后即实现了抽象类也实现了接口
interface IParent
{
void Show();
string Type
{
get;
set;
}
event AddChildren Add;
}
abstract class AParent
{
public abstract void Show();
public abstract string Type
{
get;
set;
}
public abstract event AddChildren Add;
}
class ChildrenA :AParent, IParent
{
#region AParent、IParent 成员
public override void Show()
{
throw new NotImplementedException();
}
public override string Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override event AddChildren Add;
#endregion
}
二、抽象类
抽象类是C#里面用的相对较少的工具,不过在有些场合他比接口更有用,抽象类的作用与接口类似就是声明个框架,让继承类照着这个框架去实现,不过抽象类允许对已经确定的部分先进行实现,继承类要做的就是实现没有确定的成员(抽象成员)
abstract 修饰符可以和类、方法、属性、索引器及事件一起使用。
在使用抽象类的时候需要注意:
(1)抽象类和接口一样是个框架,本身不能被实例化,只能通过继承类将其实例化。
(2)抽象类不能使用sealed和static关键字,因为使用sealed关键字表示类不能被继承,而静态类也不能被继承,抽象类不被继承是完全没有意义的。
(3)实现抽象类的类必须实现抽象类的所有抽象成员(标记了abstract的成员):
{
public abstract void Show();
public abstract string Type
{
get;
set;
}
public abstract event AddChildren Add;
public int tInt
{
get
{
return 1;
}
}
}
public class Parent : AParent
{
public override void Show()
{
throw new NotImplementedException();
}
public override string Type
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override event AddChildren Add;
}
Parent类实现了AParent的所有抽象成员,但是并没有实现AParent的非抽象成员public int tInt
(4)抽象成员都是虚成员(virtual),这一点上面的代码中可以看到,实现抽象类的成员都使用了关键字override(实现抽象成员必须要用override)表示重写抽象类的抽象成员
(5)抽象成员不能标记为static(原因同接口)、virtual,且抽象成员不能是private的(原因就是private成员无法被继承)。
(6)抽象类的抽象成员可以重写(override)基类成员也可以隐藏(new)基类成员,但是切记抽象成员不能被继承成员隐藏(new)因为被隐藏的抽象成员无法被实现:
{
public abstract void Call();
}
abstract class ACB : ACA
{
public override abstract void Call();
}
如上所示ACB的抽象成员就重写了父类ACA的抽象成员
但是切记抽象成员不能被继承成员隐藏(new关键字),例如下面的代码就会报错:
{
public abstract void Call();
}
abstract class ACB : ACA
{
public new abstract void Call();
}
上面的代码编译时会得到错误提示:错误 1 “ACB.Call()”隐藏继承的抽象成员“ACA.Call()”。意思就是说基抽象类ACA的抽象函数ACA.Call()还没有被任何类实现,就被子抽象类ACB的抽象函数ACB.Call()给隐藏了,那么基抽象类ACA的抽象函数ACA.Call()就永远无法被继承于子抽象类ACB的任何子类实现了,抽象成员不能被继承链上的子成员实现是毫无意义的,因此上面的代码会报错。
这也是为什么子类成员只能用override关键字而不能用new关键字来实现基类抽象成员的原因,如下代码所示:如果子类ACB的成员函数ACB.Call妄图用new关键字来实现基类ACA的抽象函数ACA.Call也会得到错误提示:错误 1 类“ACB”不能实现继承的抽象成员“ACA.Call()”,因为ACB.Call并没有实现基类ACA的抽象函数ACA.Call(),而是将其隐藏了,对于子类ACB的子类来说基类ACA的抽象函数ACA.Call将永远不可见,所以基类ACA的抽象函数ACA.Call()将永远无法被继承链上的任何函数实现,这是毫无意义的。所以记住子类成员只能通过override关键字来实现基类抽象成员,这样在调用基类抽象成员时,才会调用相应的子类成员,否则基类抽象成员永远都无法被调用和实现。
{
public abstract void Call();
}
class ACB : ACA
{
public new void Call()
{
throw new NotImplementedException();
}
}
(7)抽象类也支持部分定义,例如可以将(3)中的AParent分成3部分定义,其效果和定义成一个相同:
{
public abstract void Show();
public int tInt
{
get
{
return 1;
}
}
}
public abstract partial class AParent
{
public abstract string Type
{
get;
set;
}
}
public abstract partial class AParent
{
public abstract event AddChildren Add;
}
(8)抽象类可以实现接口
但是抽象类实现接口必须要实现接口成员例如:
{
void Method();
}
public abstract class AC : IA
{
public void Method()
{
Console.WriteLine("This Method in Class EC!");
}
}
另外也可以将接口成员声明为抽象成员等待子非抽象类来实现,例如:
{
void Method();
}
public abstract class AC : IA
{
public abstract void Method();
}
public class EC : AC
{
public override void Method()
{
Console.WriteLine("This Method in Class EC!");
}
}
(9)抽象类继承抽象类
抽象类在继承抽象类的时候,对于基抽象类的成员子抽象类要么不实现:
{
public abstract void Method();
}
public abstract class BC : AC
{
}
要么重写
{
public abstract void Method();
}
public abstract class BC : AC
{
public abstract override void Method();
}
也可以实现
{
public abstract void Method();
}
public abstract class BC : AC
{
public override void Method()
{
//.
}
}
但是切记不能隐藏,这一点在(6)就说到了,这里再强调下隐藏会对子类屏蔽基类成员,设想一下如果隐藏了基抽象类的成员那么基抽象类的抽象成员对于子实现类来说不可见,子实现类实现的成员是子抽象类的隐藏成员(子抽象类的隐藏成员和基抽象类同名被隐藏成员在继承链中被视为是不同的,实现子抽象类成员不了等于实现基抽象类同名被隐藏成员),基抽象类的成员就无法实现了,所以抽象成员绝对不能被子抽象类隐藏,下面的例子就会得到错误:错误 1 “Class.BC.Method()”隐藏继承的抽象成员“Class.AC.Method()”
{
public abstract void Method();
}
public abstract class BC : AC
{
public abstract new void Method();
}
public class EC : AC
{
public override void Method()
{
Console.WriteLine("This Method in Class EC!");
}
}
关于抽象类和接口,可以参考下面几篇微软官方文档的介绍:
Abstract and Sealed Classes and Class Members (C# Programming Guide)