@kuier1992
2015-09-02T09:39:04.000000Z
字数 3838
阅读 2608
C#
关于多继承的概念,许多程序员并不陌生,它指一个类从两个或多个基类派生的能力。CLR不支持多继承,CLR指示通过接口提供了“缩水版”的多继承。
Microsoft.NET Framework提供了Object类,它定义了4个公共方法:ToString,Equals,GetHashCode和GetType。该类是所有其他类的根或终极基类。换言之,所有类都继承了Object的4个实例方法。
在CLR中,任何类肯定从一个(而且只能从一个)类派生,后者最终从Object类派生。这个类称为基类。基类提供了一组方法签名和方法的实现。
CLR还允许开发人员定义接口,接口实际只是对一组方法签名进行了统一命名。这些方法不提供任何实现。类通过继承接口名称来继承接口,而且必须显式实现接口方法,否则CLR会认为此类型定义无效。C#编译器和CLR允许一个类继承多个接口,当然继承的所有接口的方法都必须实现。
类继承的一个重要特点是,凡是能够使用基类型实例的地方,都能使用派生类型的实例。接口继承的一个重要特点是,凡是能使用具有接口类型的实例的地方,都能使用实现了接口的一个类型的实例。
接口是对一组方法签名进行了统一命名。注意,接口还能定义事件、无参属性、有参属性(C#索引器),这些本质上都是方法。
C#使用interface关键字定义接口
public interface IDisposable{void Dispose();}...publice interface IEnumerable{IEnumerator GetEnumerator();}
在CLR看来,接口定义就是类型定义,接口通常I开头,目的是方便在源代码中辨认接口类型。
接口定义可从另一个或多个接口中“继承”,即将其他接口的协定包括到新接口中。例如ICollection<T>接口定义就包含了IEnumerable<T>和IEnumerabel两个接口的定义。这意味着:
继承
ICollection<T>接口的任何类必须实现ICollection<T>、IEnumerable<T>、IEnumerable这三个接口所定义的方法。
任何代码在用对象时,如果期待该对象的类型实现了ICollection<T>接口,可以认为该对象还实现了IEnumerable<T>、IEnumerable接口。
public interface IComparable<in T>{Int32 CompareTo(T outher);}...public sealed class Point : IComparable<Point> {private Int32 m_x, m_y;public Point(Int32 x, Int32 y) {m_x = x;m_y = y;}// This method implements IComparable<T> for Pointpublic Int32 CompareTo(Point other) {return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)- Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y));}public override String ToString() {return String.Format("({0}, {1})", m_x, m_y);}}...public static void Go() {Point[] points = new Point[] {new Point(3, 3),new Point(1, 2),};if (points[0].CompareTo(points[1]) > 0) {Point tempPoint = points[0];points[0] = points[1];points[1] = tempPoint;}Console.WriteLine("Points from closest to (0, 0) to farthest:");foreach (Point p in points)Console.WriteLine(p);}
编译器要求将接口的方法标记为public,CLR要求将接口方法标记为virtual。不将方法标记为virtual,编译器会将它们标记为virtual和sealed:这会阻止派生类重写接口方法。将方法显示标记为virtual,编译器就会将该方法标记为virtual(并保持它的非密封状态),使派生类能够重写他。
下面例子挺有意思的,可以看一下。
internal static class InterfaceReimplementation {public static void Go() {/************************* First Example *************************/Base b = new Base();// Call's Dispose by using b's type: "Base's Dispose"b.Dispose();// Call's Dispose by using b's object's type: "Base's Dispose"((IDisposable)b).Dispose();/************************* Second Example ************************/Derived d = new Derived();// Call's Dispose by using d's type: "Derived's Dispose"d.Dispose();// Call's Dispose by using d's object's type: "Derived's Dispose"((IDisposable)d).Dispose();/************************* Third Example *************************/b = new Derived();// Call's Dispose by using b's type: "Base's Dispose"b.Dispose();// Call's Dispose by using b's object's type: "Derived's Dispose"((IDisposable)b).Dispose();}// This class is derived from Object and it implements IDisposableinternal class Base : IDisposable {// This method is implicitly sealed and cannot be overriddenpublic void Dispose() {Console.WriteLine("Base's Dispose");}}// This class is derived from Base and it re-implements IDisposableinternal class Derived : Base, IDisposable {// This method cannot override Base's Dispose. 'new' is used to indicate// that this method re-implements IDisposable's Dispose methodnew public void Dispose() {Console.WriteLine("Derived's Dispose");// NOTE: The next line shows how to call a base class's implementation (if desired)// base.Dispose();}}}
FCL的String类型继承了Object的方法签名及其实现,此外String类型还实现了几个接口:IComparable,ICloneable,IEnumerable...,这意味着String类型不需要实现(或重写)Object基类型提供的方法,但必须实现所有接口声明的方法。
String s = "Jeffrey";//S引用一个String对象,可以调用S在String,Object,IComparable...中定义的任何方法。//ICloneable变量引用同一个String对象ICloneable cloneable = s;//使用cloneable只能调用ICloneable接口声明中的任何方法
String类型的变量可以操作实现接口的所有方法,而接口类型的变量却只能操作接口中定义的任何方法和Object定义的任何方法。
1、
IS-A对比CAN-DO关系
IS-A代表属于,CAN-DO代表能做某事。
如果派生类型属于基类型,那么就用基类;如果建立的是能做的关系就用接口
2、易用性
对于开发人员,定义从基类派生的类型通常比实现接口的所有方法要容易的多。基类可提供大量功能,所以派生类型可能只需要稍加改动即可。而提供接口的话,派生类型则需要实现所有成员。
3、一致性实现
无论接口协定订立的多好,都无法保证所有人100%的实现它,而如果基类正确的话,以后根据需要稍加修改即可。
4、版本控制
向基类添加一个方法,派生类将继承该方法。而如果向接口添加新成员,则会强迫接口的继承者更改源代码并重新编译。