[关闭]
@kuier1992 2015-09-02T09:39:04.000000Z 字数 3838 阅读 2067

C#-接口

C#


关于多继承的概念,许多程序员并不陌生,它指一个类从两个或多个类派生的能力。CLR不支持多继承,CLR指示通过接口提供了“缩水版”的多继承。

类和接口继承

Microsoft.NET Framework提供了Object类,它定义了4个公共方法:ToString,Equals,GetHashCodeGetType。该类是所有其他类的根或终极基类。换言之,所有类都继承了Object的4个实例方法。
在CLR中,任何类肯定从一个(而且只能从一个)类派生,后者最终从Object类派生。这个类称为基类基类提供了一组方法签名和方法的实现
CLR还允许开发人员定义接口接口实际只是对一组方法签名进行了统一命名。这些方法不提供任何实现。类通过继承接口名称来继承接口,而且必须显式实现接口方法,否则CLR会认为此类型定义无效。C#编译器和CLR允许一个类继承多个接口,当然继承的所有接口的方法都必须实现。
类继承的一个重要特点是,凡是能够使用基类型实例的地方,都能使用派生类型的实例。接口继承的一个重要特点是,凡是能使用具有接口类型的实例的地方,都能使用实现了接口的一个类型的实例。

定义接口

接口是对一组方法签名进行了统一命名。注意,接口还能定义事件、无参属性、有参属性(C#索引器),这些本质上都是方法
C#使用interface关键字定义接口

  1. public interface IDisposable{
  2. void Dispose();
  3. }
  4. ...
  5. publice interface IEnumerable{
  6. IEnumerator GetEnumerator();
  7. }

在CLR看来,接口定义就是类型定义,接口通常I开头,目的是方便在源代码中辨认接口类型
接口定义可从另一个或多个接口中“继承”,即将其他接口的协定包括到新接口中。例如ICollection<T>接口定义就包含了IEnumerable<T>IEnumerabel两个接口的定义。这意味着:

继承ICollection<T>接口的任何类必须实现ICollection<T>IEnumerable<T>IEnumerable这三个接口所定义的方法。
任何代码在用对象时,如果期待该对象的类型实现了ICollection<T>接口,可以认为该对象还实现了IEnumerable<T>IEnumerable接口。

继承接口

  1. public interface IComparable<in T>{
  2. Int32 CompareTo(T outher);
  3. }
  4. ...
  5. public sealed class Point : IComparable<Point> {
  6. private Int32 m_x, m_y;
  7. public Point(Int32 x, Int32 y) {
  8. m_x = x;
  9. m_y = y;
  10. }
  11. // This method implements IComparable<T> for Point
  12. public Int32 CompareTo(Point other) {
  13. return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)
  14. - Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y));
  15. }
  16. public override String ToString() {
  17. return String.Format("({0}, {1})", m_x, m_y);
  18. }
  19. }
  20. ...
  21. public static void Go() {
  22. Point[] points = new Point[] {
  23. new Point(3, 3),
  24. new Point(1, 2),
  25. };
  26. if (points[0].CompareTo(points[1]) > 0) {
  27. Point tempPoint = points[0];
  28. points[0] = points[1];
  29. points[1] = tempPoint;
  30. }
  31. Console.WriteLine("Points from closest to (0, 0) to farthest:");
  32. foreach (Point p in points)
  33. Console.WriteLine(p);
  34. }

编译器要求将接口的方法标记为public,CLR要求将接口方法标记为virtual。不将方法标记为virtual,编译器会将它们标记为virtualsealed:这会阻止派生类重写接口方法。将方法显示标记为virtual,编译器就会将该方法标记为virtual(并保持它的非密封状态),使派生类能够重写他。
下面例子挺有意思的,可以看一下。

  1. internal static class InterfaceReimplementation {
  2. public static void Go() {
  3. /************************* First Example *************************/
  4. Base b = new Base();
  5. // Call's Dispose by using b's type: "Base's Dispose"
  6. b.Dispose();
  7. // Call's Dispose by using b's object's type: "Base's Dispose"
  8. ((IDisposable)b).Dispose();
  9. /************************* Second Example ************************/
  10. Derived d = new Derived();
  11. // Call's Dispose by using d's type: "Derived's Dispose"
  12. d.Dispose();
  13. // Call's Dispose by using d's object's type: "Derived's Dispose"
  14. ((IDisposable)d).Dispose();
  15. /************************* Third Example *************************/
  16. b = new Derived();
  17. // Call's Dispose by using b's type: "Base's Dispose"
  18. b.Dispose();
  19. // Call's Dispose by using b's object's type: "Derived's Dispose"
  20. ((IDisposable)b).Dispose();
  21. }
  22. // This class is derived from Object and it implements IDisposable
  23. internal class Base : IDisposable {
  24. // This method is implicitly sealed and cannot be overridden
  25. public void Dispose() {
  26. Console.WriteLine("Base's Dispose");
  27. }
  28. }
  29. // This class is derived from Base and it re-implements IDisposable
  30. internal class Derived : Base, IDisposable {
  31. // This method cannot override Base's Dispose. 'new' is used to indicate
  32. // that this method re-implements IDisposable's Dispose method
  33. new public void Dispose() {
  34. Console.WriteLine("Derived's Dispose");
  35. // NOTE: The next line shows how to call a base class's implementation (if desired)
  36. // base.Dispose();
  37. }
  38. }
  39. }

关于调用接口方法的更多探讨

FCL的String类型继承了Object的方法签名及其实现,此外String类型还实现了几个接口:IComparable,ICloneable,IEnumerable...,这意味着String类型不需要实现(或重写)Object基类型提供的方法,但必须实现所有接口声明的方法。

  1. String s = "Jeffrey";
  2. //S引用一个String对象,可以调用S在String,Object,IComparable...中定义的任何方法。
  3. //ICloneable变量引用同一个String对象
  4. ICloneable cloneable = s;
  5. //使用cloneable只能调用ICloneable接口声明中的任何方法

String类型的变量可以操作实现接口的所有方法,而接口类型的变量却只能操作接口中定义的任何方法和Object定义的任何方法。

设计:基类还是接口

1、IS-A对比CAN-DO关系
IS-A代表属于CAN-DO代表能做某事
如果派生类型属于基类型,那么就用基类;如果建立的是能做的关系就用接口
2、易用性
对于开发人员,定义从基类派生的类型通常比实现接口的所有方法要容易的多。基类可提供大量功能,所以派生类型可能只需要稍加改动即可。而提供接口的话,派生类型则需要实现所有成员
3、一致性实现
无论接口协定订立的多好,都无法保证所有人100%的实现它,而如果基类正确的话,以后根据需要稍加修改即可。
4、版本控制
向基类添加一个方法,派生类将继承该方法。而如果向接口添加新成员,则会强迫接口的继承者更改源代码并重新编译。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注