《CLR Via C# 第3版》笔记之(十五) - 接口
接口(interface)和类(class)是CLR中应用最为广泛的两个概念。灵活的应用接口,可以构造出各种经典的设计模式。
接口的语法并不复杂,本篇主要记录接口中一些容易忽略的地方,以及如何更好的使用接口。
主要内容:
- 接口的继承
- 显式接口
- 泛型接口和约束
- 接口和抽象类
1. 接口的继承
当子类继承父类后,父类继承的接口也一并继承了过来。如下例中的类Sub
当子类继承父类后,子类可以再次继承父类已经继承的接口。如下例中的类Sub2
这两者的区别在对接口方法调用,参见下面代码中的注释。
using System; sealed class CLRviaCSharp_15 { static void Main(string[] args) { Sub s = new Sub(); // 类Sub自身的Show方法 s.Show(); // 类Base的Show方法,即继承自IShowMessage的Show方法 ((Base)s).Show(); // 类Base继承自IShowMessage的Show方法 ((IShowMessage)s).Show(); Console.WriteLine("============================="); Sub2 s2 = new Sub2(); // 类Sub2自身的Show方法,即继承自IShowMessage的Show方法 s2.Show(); // 类Base的Show方法 ((Base)s2).Show(); // 类Sub2继承自IShowMessage的Show方法 ((IShowMessage)s2).Show(); Console.ReadKey(); } } interface IShowMessage { void Show(); } class Base : IShowMessage { public void Show() { Console.WriteLine("IShowMessage"); } } /// <summary> /// 当子类继承父类后,父类继承的接口也一并继承了过来 /// </summary> class Sub : Base { // 类Sub本身的Show方法,与Base中继承IShowMessage的Show无关 public new void Show() { Console.WriteLine("Sub"); } } /// <summary> /// 子类继承父类后,子类可以再次继承父类已经继承的接口 /// </summary> class Sub2 : Base, IShowMessage { // 类Sub2继承IShowMessage的Show方法, // 与Base中继承IShowMessage的Show无关 public new void Show() { Console.WriteLine("Sub2"); } }
2. 显式接口
在上例中,类Base继承IShowMessage之后,就不能定义与 IShowMessage中签名相同的方法了。
如上例中,类Base无法再定义与方法Show相同签名的方法了。
为了解决这种情况,C#中还提供了显式接口的定义方法。
class Base : IShowMessage { public void Show() { Console.WriteLine("Base"); } void IShowMessage.Show() { Console.WriteLine("IShowMessage"); } }
这样,如果要调用IShowMessage.Show()方法,必须将Base类的实例转型成IShowMessage才行。
Base b = new Base(); b.Show(); ((IShowMessage)b).Show();
显示接口的作用主要如下:
1. 当两个接口有签名相同的方法时,一个类可以通过显示接口的方式来同时继承这两个接口。
interface IShowMessage { void Show(); } interface IPrintMessage { void Show(); } class Base : IShowMessage, IPrintMessage { public void Show() { Console.WriteLine("Base"); } void IShowMessage.Show() { Console.WriteLine("IShowMessage"); } void IPrintMessage.Show() { throw new NotImplementedException(); } }
2. 通过显式接口来增强类型安全性,从而减少装箱操作,提高性能
首先是实现隐式接口的例子:
using System; sealed class CLRviaCSharp_15 { static void Main(string[] args) { Base b = new Base(); b.Show(10); Console.ReadKey(); } } interface IShowMessage { void Show(Object o); } class Base : IShowMessage { #region IShowMessage Members public void Show(object o) { Console.WriteLine(o.ToString()); } #endregion }
在调用b.Show(10)时发生装箱操作,通过以下的IL代码(IL_00a)可以看出
.method private static hidebysig void Main ( string[] args ) cil managed { // Method begins at RVA 0x217c // Code size 28 (0x1c) .maxstack 2 .entrypoint .locals init ( [0] class Base b ) IL_0000: nop IL_0001: newobj instance void Base::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.s 10 IL_000a: box int32 IL_000f: callvirt instance void Base::Show(object) IL_0014: nop IL_0015: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_001a: pop IL_001b: ret } // End of method CLRviaCSharp_15.Main
通过显示实现接口,可以在类Base中定义输出int型的Show方法,代码如下:
using System; sealed class CLRviaCSharp_15 { static void Main(string[] args) { Base b = new Base(); b.Show(10); Console.ReadKey(); } } interface IShowMessage { void Show(Object o); } class Base : IShowMessage { public void Show(int i) { Console.WriteLine(i.ToString()); } #region IShowMessage Members void IShowMessage.Show(object o) { throw new NotImplementedException(); } #endregion }
查看Main函数的IL代码,已经没有了之前的装箱操作
.method private static hidebysig void Main ( string[] args ) cil managed { // Method begins at RVA 0x217c // Code size 23 (0x17) .maxstack 2 .entrypoint .locals init ( [0] class Base b ) IL_0000: nop IL_0001: newobj instance void Base::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.s 10 IL_000a: callvirt instance void Base::Show(int32) IL_000f: nop IL_0010: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_0015: pop IL_0016: ret } // End of method CLRviaCSharp_15.Main
显式接口还有一点需要注意的地方是,显式接口不能由派生类调用。
3. 泛型接口和约束
泛型是之前看过的概念,在接口中使用泛型,同样可以获得泛型带给我们的种种好处。
1. 提供类型安全性
比如IComparable接口和IComparable<Int32>接口相比,后者提供了类型安全检查。
int x = 1; IComparable c1 = x; // 编译通过,但是运行时异常 c1.CompareTo("2"); IComparable<Int32> c2 = x; // 编译不通过,类型不匹配 c2.CompareTo("2");
2. 减少装箱次数
还是用IComparable接口和IComparable<Int32>接口做比较。
int x = 1, y = 2; // 此处装箱一次 (x装箱) IComparable c1 = x; // 此处装箱一次 (y装箱) c1.CompareTo(y); // 此处装箱一次 (x装箱) IComparable<Int32> c2 = x; // 此处不用装箱,因为类型参数T为Int32 c2.CompareTo(y);
3. 一个类可以实现同泛型参数类型不同的同一个接口,如下所示
class MyClass : IComparable<Int32>,IComparable<string> { #region IComparable<int> Members public int CompareTo(int other) { throw new NotImplementedException(); } #endregion #region IComparable<string> Members public int CompareTo(string other) { throw new NotImplementedException(); } #endregion }
4. 接口和基类
“接口和基类的关系,以及何时使用接口,何时使用基类。”是在设计时经常需要考虑的问题。
已经有很多文章对此进行了讨论,这里要说的是这两件事可以同时做。
即:定义一个接口,同时提供一个实现了这个接口的基类。
.net Framework中的就有这样的例子,比如IComparable接口就有Comparable类提供的默认实现。
比如下面的例子,即使MyClass中没有实现IShowMessage的代码也无所谓,因为ShowMessage提供了默认实现。
interface IShowMessage { void Show(Object o); } class ShowMessage : IShowMessage { #region IShowMessage Members public void Show(object o) { Console.WriteLine(o.ToString()); } #endregion } class MyClass : ShowMessage , IShowMessage { }