@kuier1992
2015-09-02T17:39:04.000000Z
字数 3838
阅读 2369
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 Point
public 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 IDisposable
internal class Base : IDisposable {
// This method is implicitly sealed and cannot be overridden
public void Dispose() {
Console.WriteLine("Base's Dispose");
}
}
// This class is derived from Base and it re-implements IDisposable
internal class Derived : Base, IDisposable {
// This method cannot override Base's Dispose. 'new' is used to indicate
// that this method re-implements IDisposable's Dispose method
new 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、版本控制
向基类添加一个方法,派生类将继承该方法。而如果向接口添加新成员,则会强迫接口的继承者更改源代码并重新编译。