[C#1] 11-接口
接口与继承
CLR规定一个类型只能有一个基类型,这种继承成为单继承;
接口继承是指一个类型继承的是接口中的方法签名,而非方法实现,通常称为实现接口; 接口仅仅是含有一组虚方法的抽象类型,不含有任何实现。CLR允许接口包含静态方法、静态字段、常量、以及静态构造器, 但是CLS兼容的接口类型是不允许有任何静态成员的,因为一些编程语言不能定义或者访问它们。 C#语言就是如此,C#编译器不允许接口中有任何静态成员。
约定接口名称第一个字母是大写的I;接口可以多继承,实际上实现了多个接口的类型允许我们将它的对象看作这个接口中的任意一个。 就好比是一个人有很多能力,他会游泳[可以看多是运动员],他会编程[程序员]。 值类型也可以实现接口,当我们把一个值类型实例转为接口类型时,会被装箱,因为接口总被认为是引用类型,并且它们的方法总是虚方法。未装箱的值类型是没有方法表指针的,执行装箱将使CLR可以查询类型的方法表,便可以调用其虚方法了。
抽象类:is-a的关系;接口:can-do的关系。
使用接口改变已装箱值类型中的字段
public struct Location { public int x, y; public void Change(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return string.Format("[{0}-{1}]", x.ToString(), y.ToString()); } } public class Test { static void Main() { Location l = new Location(); l.x = l.y = 6; Console.WriteLine(l);//[6-6] l.Change(5, 5); Console.WriteLine(l);//[5-5] object o = l; Console.WriteLine(o);//[5-5] //o对Change方法一无所知,所以先转型为Location //然而这样会执行拆箱操作,在线程堆栈上生成一个 //临时的Location,当改变它的字段时,原有的已装 //箱的<o>则不受这样的影响 ((Location)o).Change(9, 9); //[5-5] Console.WriteLine(o); } }
上述代码我们无法改变以装箱的值类型中字段,我们可以用接口来欺骗C#使其改变值类型的字段[Location : IChangeBoxedLocation]:
1 public interface IChangeBoxedLocation 2 { 3 void Change(int x, int y); 4 } 5 public class Test 6 { 7 static void Main() 8 { 9 Location l = new Location(); 10 l.x = l.y = 6; 11 object o = l; 12 //转型为接口须装箱,改变已装箱的对象,最后丢弃改变 13 ((IChangeBoxedLocation)l).Change(5, 5); 14 Console.WriteLine(l);//[6-6] 15 16 //因为o已经是引用类型,接口方法Change允许我们修改o中的字段 17 ((IChangeBoxedLocation)o).Change(9, 9); 18 Console.WriteLine(o);//[9-9] 19 } 20 }
我的通俗理解是:我是一个人[值类型Location],我有编程的能力[实现IChangeBoxedLocation接口], 所以在必要的时候你可以把我当作程序员来使用。
实现有多个相同方法的接口
1 public interface IWindow 2 { 3 void Print(); 4 } 5 6 public interface IConsole 7 { 8 void Print(); 9 } 10 11 public class MyClass : IWindow, IConsole 12 { 13 void IWindow.Print() 14 { 15 //.... 16 } 17 void IConsole.Print() 18 { 19 //.... 20 } 21 public void Print() 22 { 23 //.... 24 } 25 }
MyClass实现了多了Print方法,所以我们要告诉C#编译器我们的哪一个Print实现了哪个接口,C#中通过在方法名前面加上接口名来告诉C#编译器。public void Print()则是一个普通的方法而已,与接口没有任何关系【如果我们把void IWindow.Print()删除,则这个方法[public void Print()]将是实现IWindow接口的方法,C#编译器在辨析接口成员实现是,会按照先完全限定接口成员后非完全限定成员的顺序进行辨析】。
上面的两个完全限定接口方法没有声明为public,这是因为这些方法会有双重身份,有时共有[类型转型为该接口类型时:MyClass转为 IWindow或者 IConsole时],有时私有[MyClass实例时]。在一个类型中用完全限定名定义接口方法时,该方法被认为是私有的,因为类型本身无法调用它,当转型为一个接口时,这个方法将可以被调用,这时又是一个共有方法
显示接口成员实现
显示实现接口成员正是用到了上面的用完全限定名来实现接口。显示实现接口成员为应用程序提供了更多的类型安全。