[CLR via C#]13. 接口
一、类和接口继承
在Microsoft.Net Framwork中,有一个名为System.Object的类,它定义了4个公共实例方法:ToString, Equals, GetHashCode和GetType。该类是其他所有类的根或者说最终基类。换言之,所有类都继承了Object的4个实例方法。这还意味着能操作Object类的实例的代码实际能操作任何类的实例。
在CLR中,任何类都肯定从一个类也只能从一个类(而且只能从Objetc派生的类)派生的。这个类称为基类。基类提供了一组方法签名和这些方法的实现。你定义的新类可在将来由其它开发人员用作基类——所有方法签名和方法实现都会由新的派生类继承。
二、定义接口
接口对一组方法签名进行了统一命名。接口还能定义事件,无参属性和有参属性(C#中的索引器),因为这些东西本质都是方法。接口不能定义任何构造器方法,和任何实例字段。
public interface IDisposable {
void Dispose();
} public interface IEnumerable {
IEnumerator GetEnumerator();
}
对CLR而言,定义接口就像定义类型,也就是说,CLR会为接口类型对象定义一个内部数据结构,同时可用反射机制来查询接口类型的功能。定义接口类型时,可指定你希望的任何访问性修饰符。
三、继承接口
本节将讲介绍如何定义一个实现了接口的类型,然后再介绍如何创建这个类型的一个实例,并用这个对象调用接口的方法。最后介绍C#接口实现时,幕后发生的事情。
public interface IComparable<in T> {
Int32 CompareTo(T other);
}
以下代码展示类如何定义一个实现类该接口的类型,还展示了对象两个Point对象进行比较的代码:
// Point 从 System.Object 派生并且实现了 IComparable<T>.
public sealed class Point : IComparable<Point>
{
private Int32 m_x, m_y; public Point(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
} // 该方法实现了 IComparable<T>.CompareTo()
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(, ),
new Point(, ),
};
//调用了由Point实现的IComparable<T>.CompareTo()方法
if (points[].CompareTo(points[]) > )
{
Point tempPoint = points[];
points[] = points[];
points[] = tempPoint;
}
Console.WriteLine("Points from closest to (0, 0) to farthest:");
foreach (Point p in points)
Console.WriteLine(p);
}
internal static class InterfaceReimplementation
{
public static void Go()
{
/************************* 第一个例子 *************************/
Base b = new Base(); // 结果显示: "Base's Dispose"
b.Dispose(); // 结果显示: "Base's Dispose"
((IDisposable)b).Dispose(); /************************* 第二个例子 ************************/
Derived d = new Derived(); // 结果显示: "Derived's Dispose"
d.Dispose(); // 结果显示: "Derived's Dispose"
((IDisposable)d).Dispose(); /************************* 第三个例子 *************************/
b = new Derived(); // 结果显示: "Base's Dispose"
b.Dispose(); // 结果显示: "Derived's Dispose"
((IDisposable)b).Dispose();
} // 这个类型派生自 Object 并且实现了 IDisposable
internal class Base : IDisposable
{
// 这个方法是隐式密封的,不能被重写
public void Dispose()
{
Console.WriteLine("Base's Dispose");
}
} // 这个类继承了Base并且实现了IDisposable接口
internal class Derived : Base, IDisposable
{
// 这个方法不能重写 Base's Dispose.
// 'new' 关键字表明重新实现了IDisposable的Dispose
new public void Dispose()
{
Console.WriteLine("Derived's Dispose"); // 注意: 下一行展示了如何让调用基类的方法
// base.Dispose();
}
}
}
// s变量引用了String对象,使用s时,可以调用String,
// Object,IComparable等定义的方法
private String s = "abc"; // comparable变量引用指向同一个String对象,使用comparable
// 只能调用IComparable接口中的定义任何方法(包括Objetc中的方法)
private IComparable comparable = s; // convertible变量引用指向同一个String对象,使用convertible
// 只能调用IConvertible接口中的定义任何方法(包括Objetc中的方法)
private IConvertible convertible = s; // enumerable 变量引用同一个String对象
// 在运行时,可将变量从一种接口类型转型到另一种类型,
// 只要该对象的类型实现了这两个个接口
// 使用enumerable,只能调用IEnumerable声明的任何方法(包括Objetc中的方法)
private IEnumerable enumerable = (IEnumerable) convertible;
interter sealed class SimpleType : IDisposable {
public void Dispose() { console.WriteLine("Dispose"); }
}
类型的方法表将包含以下方法对应的记录项:
internal static class ExplicitInterfaceMethodImpl
{
public static void Go()
{
SimpleType st = new SimpleType(); // 调用公共的 Dispose 方法实现
st.Dispose(); // 调用 IDisposable 的 Dispose 方法实现
IDisposable d = st;
d.Dispose();
} public sealed class SimpleType : IDisposable
{
public void Dispose() { Console.WriteLine("Dispose"); }
}
}
执行后,两者是没有任何却别的。输出结果都是Dispose
public sealed class SimpleType : IDisposable
{
public void Dispose() { Console.WriteLine("public Dispose"); }
void IDisposable.Dispose() { Console.WriteLine("IDisposable Dispose"); }
}
public Dispose
IDisposable Dispose
在C#中,将定义的那个接口的名称作为方法名的前缀(例如IDisposable.Dispose),创建的就是显式接口方法实现(EIMI)。注意,在C#中定义一个显式接口方法时,不允许指定可访问性。但是,编译器生成的元数据时,其访问性会被自动设为private,防止其他代码在使用类的实例时直接调用接口方法。要调用接口方法,只能通过接口类型的一个变量来进行。
private static void SomeMethod1()
{
Int32 x = 1, y = 2;
IComparable c = x; // CompareTo 期望接口一个 Object 类型; 传递 y (一个 Int32 类型) 允许
c.CompareTo(y); // Boxing occurs here // CompareTo期望接口一个 Object 类型; 传递 "2" (一个 String 类型) 允许
// 但运行是抛出 ArgumentException 异常
c.CompareTo("2");
}
在理想情况下,接口方法应该使用强类型。这也正是FCL为什么还有包含一个泛型IComparable<in T>接口的原因。
private static void SomeMethod2()
{
Int32 x = 1, y = 2;
IComparable<Int32> c = x; // CompareTo 期望接口一个 Int32 类型; 传递 y (一个 Int32 类型) 允许
c.CompareTo(y); // Boxing occurs here // CompareTo 期望接口一个 Int32 类型; 传递 "2" (一个 String 类型) 编译不通过
// 指出 String 不能被隐式转型为 Int32
// c.CompareTo("2");
}
public static void Go()
{
Number n = new Number(); // n 与 一个 Int32类型 5 作比较
IComparable<Int32> cInt32 = n;
Int32 result = cInt32.CompareTo(5); // n 与一个 String类型 "5" 作比较
IComparable<String> cString = n;
result = cString.CompareTo("5");
} // 该类实现了 IComparable<T> 接口两次
public sealed class Number : IComparable<Int32>, IComparable<String>
{
private Int32 m_val = 5; // 该方法实现了 IComparable<Int32>’s CompareTo
public Int32 CompareTo(Int32 n)
{
return m_val.CompareTo(n);
} // 该方法实现了 IComparable<String>’s CompareTo
public Int32 CompareTo(String s)
{
return m_val.CompareTo(Int32.Parse(s));
}
}
public sealed class SomeType
{
private static void Test()
{
Int32 x = ;
Guid g = new Guid(); // 对M的调用能通过编译,因为Int32实现了IComparable 和 IConvertible
M(x); // 对M的调用能不通过编译,因为Guid实现了IComparable,但没实现了 IConvertible
// M(g);
} // M类型参数T被约束为需要支持同时实现IComparable 和 IConvertible interfaces接口的类型
private static Int32 M<T>(T t) where T : IComparable, IConvertible
{
// ...
return ;
}
}
public interface IWindow
{
Object GetMenu();
} public interface IRestaurant
{
Object GetMenu();
}
要定义一个实现了这两个接口的类型,必须使用"显示接口方法实现"来实现这个类型的成员,如下:
// 这个类型派生自 System.Object and
// 并不实现 IWindow 和 IRestaurant 接口.
public class MarioPizzeria : IWindow, IRestaurant
{
// 这是IWindow 的 GetMenu 方法.
Object IWindow.GetMenu()
{
// ...
return null;
} // 这是 IRestaurant 的 GetMenu 方法.
Object IRestaurant.GetMenu()
{
// ...
return null;
} // 这个GetMenu方法是可选的,与接口无关
public Object GetMenu()
{
// ...
return null;
}
}
}
public static void Go()
{
MarioPizzeria mp = new MarioPizzeria(); // 这行调用 MarioPizzeria 的公共 GetMenu 方法
mp.GetMenu(); // 这行调用 MarioPizzeria 的 IWindow.GetMenu 方法
IWindow window = mp;
window.GetMenu(); // 这行调用 MarioPizzeria 的 IRestaurant.GetMenu 方法
IRestaurant restaurant = mp;
restaurant.GetMenu();
}
public interface IComparable {
Int32 CompareTo(Objetc other);
}
这个接口定义了一个方法,该方法接受一个System.Object类型的参数。可像下面一样实现该接口的一个类型:
internal struct SomeValueType : IComparable
{
private Int32 m_x;
public SomeValueType(Int32 x) { m_x = x; }
public Int32 CompareTo(Object other)
{
return (m_x - ((SomeValueType)other).m_x);
}
}
public static void Go()
{
SomeValueType v = new SomeValueType();
Object o = new Object();
Int32 n = v.CompareTo(v); // 非预期装箱
n = v.CompareTo(o); // 抛出异常
}
internal struct SomeValueType : IComparable
{
private Int32 m_x;
public SomeValueType(Int32 x) { m_x = x; }
public Int32 CompareTo(SomeValueType other)
{
return (m_x - other.m_x);
}
// 注意: 这个是显示实现接口
Int32 IComparable.CompareTo(Object other)
{
return CompareTo((SomeValueType)other);
}
}
注意新版本的几个改动。第一,他现在有两个CompareTo方法。第一个CompareTo方法不是获取一个Object作为参数,而是获取一个SomeValueType作为参数。这个参数改变后,就没必要再用代码将other类型转型为SomeValueType,所以用于转型的代码被去掉了。第二,修改第一个CompareTo方法,使它类型安全。这意味着SomeValueType现在没有实现ICompareTo接口,所以不满足IComparable接口的契约。所以,SomeValueType必须实现一个CompareTo方法来满足IComparable的契约。第二个IComparable.CompareTo方法正是出于这个目的而设计的,它是一个EIMI。
这两个改动意味着想在获得了编译时的类型安全性,而且不会发生装箱:
public static void Go()
{
SomeValueType v = new SomeValueType();
Object o = new Object();
Int32 n = v.CompareTo(v); // 不发生装箱
n = v.CompareTo(o);
}
事实上,如本章前面所述,将一个值类型的实例转型为接口类型时,CLR必须对之类实例的实例进行装箱。因此,前面的Main方法中会发生两次装箱。
不过,如果定义一个接口类型的变量,就会再次丧失编译时的类型安全性,而且再次发生非预期的装箱操作。
public static void Go()
{
SomeValueType v = new SomeValueType();
IComparable c = v; //装箱 Object o = new Object();
Int32 n = v.CompareTo(v); // 非预期装箱操作
n = c.CompareTo(o); //InvalidCastException异常
}
事实上,如本章前面所述,将一个值类型的实例转型为接口类型时,CLR必须对指向类型的实例进行转型。因此,前面的Go方法中会发生两次装箱。
实现IConvertible, ICollection,IList和IDictionary等接口时,可利用EIMI为这些接口的方法创建类型安全的版本,并减少值类型的装箱。
十一、设计:基类还是接口
选择基类还是接口的指导性原则:
[CLR via C#]13. 接口的更多相关文章
- 重温CLR(九) 接口
对于多继承(multiple inheritance)的概念,许多程序员并不陌生,他是指一个类从两个或多个基类派生的能力.例如,假定TransmitData类的作用是发送数据,ReceiveData类 ...
- CLR设计类型之接口
写在前面的话: 写到这一节的时候,CLR设计类型就已经结束了,因为CLR要求的是有一定基础的人看的,所以我们不是从基础类型以及运算符开始的,文章从一开始就讲的是深入面向对象编程 ...
- 【C#进阶系列】13 接口
C#不支持类的多继承,然而却可以继承多个接口.简单的就不说了,来看看下面的例子: public interface IRead { string GetText(); } public interfa ...
- java基础13 接口(及关键字:interface、implements)
接口 1.接口的定义格式 interface 接口名{ } interface :接口的关键字 implements:实现接口的关键字 2.接口的作用 1.程序的解耦.(低耦合) 2.定 ...
- 【CLR VIA C#】读书笔记
工作几年了才看,记录下笔记备忘. 章节 笔记 1.CLR的执行模型 公共语言运行时(Common Language Runtime,CLR) 源代码-->编译器检查语法和分析源代码-->托 ...
- C#进阶系列 ---- 《CLR via C#》
[C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...
- 第二十二章 CLR寄宿和AppDomain
1. 概念解析 CLR Hosting(CLR 宿主):初始启动.Net Application时,Windows进程的执行和初始化跟传统的Win32程序是一样的,执行的还是非托管代码,只不过由于PE ...
- Go语言学习之5 进阶-排序、链表、二叉树、接口
本节主要内容: 1. 结构体和方法2. 接口 1. 结构体和方法 (1). 用来自定义复杂数据结构 (2). struct里面可以包含多个字段(属性) (3). struct类型可以定 ...
- 第22章 CLR寄宿和AppDomain
22.1 CLR寄宿 CLR Hosting(CLR 宿主)的概念:初始启动.Net Application时,Windows进程的执行和初始化跟传统的Win32程序是一样的,执行的还是非托管代码,只 ...
随机推荐
- Centos单网卡配置多个IP的方法
网上好多介绍一个网卡多个IP的方法,都有些问题,下面是实践可行的方法 DEVICE="eth0" TYPE="Ethernet" BOOTPROTO=none ...
- MediaElement视频控制:播放、暂停、停止、后退、快进、跳转、音量
/* ================================================= * Author: Micro * Date: 2016=03-25 ...
- 《objective-c基础教程》学习笔记(八)—— 拆分接口和实现
在之前的项目中,我们编程都是直接写在一个main.m文件中.类的main()函数,@interface和@implementation部分都塞进一个文件.这种结构对于小程序和简便应用来说还可以.但是项 ...
- Java WebService 简单实例[转]
http://www.cnblogs.com/yisheng163/p/4524808.html?utm_source=tuicool 前言:朋友们开始以下教程前,请先看第五大点的注意事项,以避免不必 ...
- iOS 日期处理 (Swift3.0 NSDate)
处理日期的常见情景 NSDate -> String & String -> NSDate 日期比较 日期计算(基于参考日期 +/- 一定时间) 计算日期间的差异 拆解NSDate ...
- MTNET 自用ios网络库开源
短短两天就在https://git.oschina.net/gangwang/MTNET这里收获15个星 github 5星, 值得收藏! MTNET 自用ios网络库开源, 自用很久了,在数歀上架的 ...
- swift 附属脚本
附属脚本是访问对象,集合或序列的快捷方式 struct STest{ let constValue:Int subscript(count:Int)->Int{ return count*con ...
- swift 方法
swift的类,结构体,枚举中都可以定义方法. 1:实例方法.类似于类成员方法 1.1实例方法是属于类,结构体,枚举的实例的方法.通过其实例访问. class CShow{ func testShow ...
- ubuntu系统从中文环境改成英文环境
我们在 安装ubuntu server版的时候,有人可能选择了中文环境安装,因为那样好设置时区等参数,可是安装好了后,运行某些命令的时候会有中文乱码提示,看起很是头蛋疼, 我们就需要将其改成英文环 ...
- iOS开发中一些常见的并行处理
本文主要探讨一些常用多任务的最佳实践.包括Core Data的多线程访问,UI的并行绘制,异步网络请求以及一些在运行态内存吃紧的情况下处理大文件的方案等.??其实编写异步处理的程序有很多坑!所以,本文 ...