并非只能通过继承使用多态性,还能通过接口使用它。
和抽象类不同,接口不包含任何实现(方法)。
然后和抽象类相似,接口也定义了一系列成员,调用者可以依赖这些成员来支持一个特定的功能。
实现接口的类会使用与被实现的接口相同的签名来定义方法。
通过基类来共享成员签名和实现,但通过接口只是共享成员签名,不共享实现。
接口的一个关键特征就是它既不包含实现,也不包含数据。
字段不能出现在一个接口中。
但是可以使用属性,由于属性不会包含任何实现作为接口声明的一部分,所以它不会引用一个支持字段。
C#不允许为接口成员使用访问修饰符。所有成员都自动定义为pulbic成员。
一旦某个类声明自己要实现一个接口,则该接口的所有成员都必须实现。
接口的一个重要特征在于,它们永远不能被实例化,不能使用new来创建一个接口。
也正是这个原因,接口不能有构造器或终结器。接口实例只适用于实现它们的类型。
除此之外,接口不能包含static成员。接口的一个重要目的就是实现多态性
显式接口实现 ,隐式接口实现
在重写的方法和属性前是否添加接口名。
1、显式成员实现
在实现的方法和属性前加上接口名。
由于显式接口实现直接与接口关联,因此没有必要使用virtual、override或者public来修饰它们。
2、隐式成员实现
不在实现的方法和属性前加上接口名。
直接使用方法名和属性名
隐式成员实现必须是public的,除此之外,virtual是可选的,具体取决于派生类是否可以重写实现。
如果去掉virtual,该成员就如同是sealed成员。且override是不允许的。
由于成员的接口声明不包含实现,所以override没有意义。
3、显式接口实现与隐式接口实现的比较。
显式接口实现是将“机制方面的考虑”与“模型方面的考虑”分隔开的一种技术。
要求调用者先将对象转换为接口,然后才能认为对象是“可比较”的,可显式地区分你想在什么时候和模型 进行沟通,
以及想在什么时候和实现机制打交道。
以下一些基本的设计原则,可以利用它们来帮助自己选择显式还是隐式实现。
成员是不是核心的类功能
是:隐式
否:显式
接口成员名作为类成员是否恰当
是:隐式
否:显式
是否已经有一个同名的类成员
是:隐式
否:显式
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 IListTable i; 7 Contact c = new Contact(); 8 c.Name = "name1"; 9 Console.WriteLine(c.ColnumName); 10 11 Appointment a = new Appointment(); 12 a.Name = "name2"; 13 Console.WriteLine(((IListTable)a).ColnumName); 14 15 Console.ReadLine(); 16 17 } 18 } 19 20 interface IListTable 21 { 22 string ColnumName 23 { get; } 24 } 25 public class Contact : IListTable 26 { 27 public string Name 28 { 29 get 30 { 31 return FirtstName; 32 } 33 set 34 { 35 FirtstName = value + " from Contact"; 36 } 37 } 38 private string FirtstName; 39 //隐式实现 40 public string ColnumName 41 { 42 get { return FirtstName + " ColnumName"; } 43 set { FirtstName = value; } 44 } 45 46 47 } 48 public class Appointment : IListTable 49 { 50 public string Name 51 { 52 get 53 { 54 return FirtstName; 55 } 56 set 57 { 58 FirtstName = value + " from Appointment"; 59 } 60 } 61 private string FirtstName; 62 63 //显式实现 64 string IListTable.ColnumName 65 { 66 get { return FirtstName + " ColnumName"; } 67 //set { FirtstName = value; }//错误 68 } 69 70 }
4、实现类与其接口之间的转型
在实现类的实例中总是包含了接口中的全部成员,所以对象总是能成功转型为接口类型。
从接口类型转为它的实现类,需要执行一次显式的转型。
5、接口继承
一个接口可以从另一个接口派生,派生的接口将继承“基接口”的所有成员。
在用于显式接口成员实现的一个完全限定的接口成员名称中,必须引用最初声明它的那个接口的名称。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 7 } 8 } 9 10 public interface IReadAbleSettingsProvider 11 { 12 string GetSetting(string name, string defaultValue); 13 } 14 public interface ISettingsProvider : IReadAbleSettingsProvider 15 { 16 void SetSetting(string name, string value); 17 } 18 class FileSettingsProvider : ISettingsProvider 19 { 20 public void SetSetting(string name, string value) 21 { 22 // 23 } 24 string IReadAbleSettingsProvider.GetSetting(string name, string defaultValue) 25 { 26 // 27 return " "; 28 } 29 } 30
即使类实现了从基接口派生的一个接口,类仍然可以公开声明两个接口的实现(两个接口都继承实现,放在限定符后面:)
1 class FileSettingsProvider : ISettingsProvider, IReadAbleSettingsProvider 2 { 3 public void SetSetting(string name, string value) 4 { 5 // 6 } 7 string IReadAbleSettingsProvider.GetSetting(string name, string defaultValue) 8 { 9 // 10 return " "; 11 } 12 }
7、多接口继承
就像类能实现多个接口那样,接口也可以从多个接口继承,而且语法与类的继承和实现的语法是一致的。
8、接口上的扩展方法
可以在别的类中为接口添加一个成员方法。(扩展方法必须在静态类中定义,且扩展方法需要static修饰)
C#不仅允许为一个特定的对象实例添加扩展方法,还通话为那些对象的一个集合添加扩展方法。对扩展方法的支持是实现
LINQ的基础 。
IEnumerable是所有集合都要实现的基础接口。
通过为IEnumberable定义扩展方法,为所有集合都添加了LINQ支持。
这显著地改变了对象集合的编程方式。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 Contact[] items = new Contact[] { new Contact(), new Contact() }; 7 for (int i = 0; i < items.Length; i++) 8 { 9 items[i].ColnumName = "name" + i; 10 } 11 items.ListColumn(); 12 13 14 15 Console.ReadLine(); 16 17 } 18 } 19 20 interface IListTable 21 { 22 string ColnumName 23 { get; } 24 } 25 public class Contact : IListTable 26 { 27 public string Name 28 { 29 get 30 { 31 return FirtstName; 32 } 33 set 34 { 35 FirtstName = value + " from Contact"; 36 } 37 } 38 private string FirtstName; 39 //隐式实现 40 public string ColnumName 41 { 42 get { return FirtstName + " ColnumName"; } 43 set { FirtstName = value; } 44 } 45 46 47 } 48 49 static class Listable 50 { 51 public static void ListColumn(this IListTable[] items) 52 { 53 string headers = ""; 54 for (int i = 0; i < items.Length; i++) 55 { 56 headers += items[i].ColnumName + ","; 57 } 58 Console.WriteLine(headers); 59 } 60 }
输出:
name0 ColnumName,name1 ColnumName,
8、通过接口实现多重继承
虽然类只能从一个基类派生,但可以实现任意数量的接口,这有效解决了C#类不支持多重继承的问题。
为此,我们要像上一章讲述的那样使用聚合,但可以稍微改变一下结构,在其中添加一个接口。
需要写代码:
1 public class PdaItem 2 { 3 public PdaItem() 4 { 5 } 6 public PdaItem(DateTime pLastUpdated) 7 { 8 9 LastUpdated = pLastUpdated; 10 } 11 public DateTime LastUpdated { set; get; } 12 } 13 interface IPerson 14 { 15 string FirstName { set; get; } 16 string LastName { set; get; } 17 } 18 public class Person : IPerson 19 { 20 public string Address { set; get; } 21 public string Phone { set; get; } 22 23 public string FirstName { set; get; } 24 public string LastName { set; get; } 25 } 26 //使用聚合(实现继承Person 和 PdaItem 两个类,并将方法和属性放到接口当中 27 public class Contact : PdaItem, IPerson 28 { 29 private Person _Person; 30 public Person person 31 { 32 set 33 { 34 _Person = value; 35 } 36 get 37 { 38 return _Person; 39 } 40 } 41 public string FirstName 42 { 43 get 44 { 45 return _Person.FirstName; 46 } 47 set 48 { 49 _Person.FirstName = value; 50 } 51 } 52 public string LastName 53 { 54 get 55 { 56 return _Person.LastName; 57 } 58 set 59 { 60 _Person.LastName = value; 61 } 62 } 63 64 }
//IPerson 确保Person的成员和复制到Contact的成员具有一致的签名,然而,这个实现仍然没有作到"多重继承"真正同义
//,因为添加到Person的新成员不会同时添加到Contact上(以上的是接口的实现没有带过来,也不能进行重写,只能利用继承这个接口,实现这个接口,重写那两个属性)。
//如果被实现的成员是方法(不是属性),那么有一个办法可以对此进行改进。具体地说,就是为从第二个基类"派生"的附加功能定义接口扩展方法。
//例如,IPerson上的一个扩展方法可以提供一个名为VerifyCredentials()的方法。这样一来,实现了IPerson的所有类(即使IPerson接口没有成员,只用扩展方法)
//都会有VerifyCredentials()的一个默认实现。这之所以可行,完全是多态性和重写的功能。之所以支持重写,是因为一个方法的任何实例实现都要优先
//具有相同静态签名的一个扩展方法。
9、版本控制
需要对程序增加功能时,不应该修改原来的接口,而是再定义一个接口,然后继承原来的接口,新接口上添加功能定义,
再使需要增加功能的类实现该接口。
10、接口与类的比较
接口引入 了另一个类别的数据类型(它们是少数不对终极基类System.Object进行扩展的类型之一,此外还有指针类型和类型参数类型)
然后,和类不同的是,接口永远不能实例化。要访问一个接口p痊,只能通过对实现了接口的一个对象的引用来进行。
不能为接口使用new运算。所以,接口不能包含任何构造器或终结器,除此之外,静态成员在接口上是不允许的。
接口近似于抽象类,两者具有一些共同的特点。