接口对一组方法签名进行了统一命名。接口允许定义事件、无参属性、有参属性(C#的索引器)。所有这些东西本质上都是方法,它们只是语法上的简化。不过,接口不能定义任何构造器方法,也不能定义任何实例字段。
虽然CLR允许接口定义静态方法、静态字段、常量和静态构造器,但符合CLS标准的接口绝不允许,因为有的编程语言不能定义或访问它们。事实上,C#禁止接口定义任何一种这样的静态成员。
public interface IDisposable {
void Dispose();
}
public interface IEnumerable {
IEnumerator GetEnumerator();
}
public interface IEnumerable<T> : IEnumerable {
new IEnumerator<T> GetEnumerator();
}
public interface ICollection<T> : IEnumerable<T>, IEnumerable {
void Add(T item);
void Clear();
Boolean Contains(T item);
void CopyTo(T[] array, Int32 arrayIndex);
Boolean Remove(T item);
Int32 Count { get; }
Boolean IsReadOnly { get; }
}
public interface IComparable<in T> {
Int32 CompareTo(T other);
}
在CLR看来,接口定义就是类型定义。也就是说,CLR会为接口类型对象定义内部数据结构,同时可通过反射机制来查询接口类型的功能。和类型一样,接口可在文件范围中定义,也可嵌套在另一个类型中。定义接口类型时,可指定你希望在任何可见性/可访问性(public, protected, internal等)。
根据约定,接口类型名称以大写字母I开头,目的是方便在源代码中辨认接口类型。CLR支持泛型接口和接口中的泛型方法。
using System;// 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; }// 该方法为 Point 实现 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 class Program { public static void Main() { Point[] points = new Point[] { new Point(3, 3), new Point(1, 2), };// 下面调用由 Point 实现的 IComparable<T>的 CompareTo 方法 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); } }
C#编译器要求将实现的接口方法标记为public。CLR要求接口方法标记为virtual。不将方法显式标记为virtual,编译器会将它们标记为virtual和sealed;这会阻止派生类重写接口方法。将方法显式标记为virtual,编译器就会将该方法标记为virtual(并保持它的非密封状态),使派生类能重写它。
派生类不能重写sealed的接口方法。但派生类可重新继承同一个接口,并为接口方法提供自己的实现。在对象上调用接口方法时,调用的是该方法在该对象的类型中的实现。下例对此进行了演示:
using System;
public static class Program {
public static void Main() {
/****************** 第一个例子 ******************/
Base b = new Base();
// 用b的类型来调用Dispose,显示; "Base's Dispose"
b.Dispose();
// 用b的对象的类型来调用Dispose,显示; "Base's Dispose"
((IDisposable)b).Dispose();
/****************** 第二个例子 ******************/
Derived d = new Derived();
// 用d的类型来调用Dispose,显示; "Derived's Dispose"
d.Dispose();
// 用d的对象的类型来调用Dispose,显示; "Derived's Dispose"
((IDisposable)d).Dispose();
/****************** 第三个例子 ******************/
b = new Derived();
// 用b的类型来调用Dispose,显示; "Base's Dispose"
b.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的Dispose,
// 'new' 表明该方法重新实现了IDisposable的Dispose方法
new public void Dispose() {
Console.WriteLine("Derived's Dispose");
// 注意,下面这行代码展示了如何调用基类的实现(如果需要的话)
// base.Dispose();
}
}
}
关于调用接口方法的更多探讨
FCL的System.String类型继承了System.Object的方法签名及其实现。此外,String类型还实现了几个接口:IComparable, ICloneable, IConvertible, IEnumerable, IComparable<String>, IEnumerable<Char>和IEquatable<String>。这意味着String类型不需要实现(或重写)其Object基类型提供的方法,但必须实现所有接口声明的方法。
CLR允许定义接口类型的字段、参数或局部变量。使用接口类型的变量可以调用该接口定义的方法。此外,CLR允许调用Object定义的方法,因为所有类都继承了Object的方法。以下代码对此进行了演示:
// s变量引用一个String对象 String s = "Jeffrey";// 可以使用s调用在String,Object,IComparable,ICloneable, // IConvertible,IEnumerable中定义的任何方法 // cloneable变量引用同一个String对象 ICloneable cloneable = s;// 使用cloneable只能调用ICloneable接口声明的 // 任何方法(或Object定义的任何方法) // comparable变量引用同一个String对象 IComparable comparable = s;// 使用comparable只能调用IComparable接口声明的 // 任何方法(或Object定义的任何方法) // enumerable变量引用同一个String对象 // 可在运行时将变量从一个接口转换成另一个,只要 // 对象的类型实现了这两个接口 IEnumerable enumerable = (IEnumerable) comparable;// 使用enumerable只能调用IEnumerable接口声明的 // 任何方法(或Object定义的任何方法)
隐式和显式接口方法实现
显式接口方法实现(Explicit Interface Method Implementation, EIMI)
internal sealed class SimpleType : IDisposable {
public void Dispose() { Console.Writeline("Dispose"); }
}
public sealed class Program {
public static void Main() {
SimpleType st = new SimpleType();
//调用公共Dispose方法实现
st.Dispose();
//调用IDisposable的Dispose方法的实现
IDisposable d = st;
d.Disapose();
}
}
Dispose Dispose
internal sealed class SimpleType : IDisposable {
public void Dispose() { Console.Writeline("public Dispose"); }
//显示实现接口,不允许指定可访问性(比如public或private)。
//但是,编译器生成方法的元数据时,可访问性会自动设为private,
//防止其他代码在使用类的实例时直接调用接口方法。
//只有通过接口类型的变量才能调用接口方法。
void IDisposable.Dispose() { Console.Writeline("IDisposable Dispose"); }
}
public sealed class Program {
public static void Main() {
SimpleType st = new SimpleType();
//调用公共Dispose方法实现
st.Dispose();
//调用IDisposable的Dispose方法的实现
IDisposable d = st;
d.Disapose();
}
}
public Dispose IDisposable Dispose
泛型接口
using System;// 该类型实现泛型IComparable<T>接口两次 public sealed class Number : IComparable<Int32>, IComparable<String> { private Int32 m_val = 5;// 该方法实现IComparable<Int32>的CompareTo方法 public Int32 CompareTo(Int32 n) { return m_val.CompareTo(n); }// 该方法实现IComparable<String>的CompareTo方法 public Int32 CompareTo(String s) { return m_val.CompareTo(Int32.Parse(s)); } } public static class Program { public static void Main() { 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"); } }
// 逆变:指可以传递给定类型的子类作为实参(即, T的子类)。 // 泛型接口的逆变需要关键字in实现 public interface IComparable<in T> { int CompareTo(T other); }// 协变:指可以返回给定类型的基类(即, T的基类)。 // 泛型接口的协变需要关键字out实现 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } //.NET 4.0之后支持协变与逆变,并且尽量使用此特性以增加接口的灵活性。
泛型和接口约束
• 泛型约束可以实现类型安全
• 传递值类型时减少装箱
// M的类型参数T被约束为只支持同时实现了 // IComparable和IConvertible接口的类型 private static Int32 M<T>(T t) where T : IComparable, IConvertible { }// 这种写法会产生装箱操作 private static Int32 M(IComparable t) { }
C#编译器为接口约束生成特殊IL指令,导致直接在值类型上调用接口方法而不装箱。不用接口约束便没有其他办法让C#编译器生成这些IL指令,如此一来,在值类型上调用接口方法总是发生装箱。一个例外是如果值类型实现了一个接口方法,在值类型的实例上调用这个方法不造成值类型的实例装箱。
实现多个具有相同方法名和签名的接口
public interface IWindow {
Object GetMenu();
}
public interface IRestaurant {
Object GetMenu();
}
public sealed class MarioPizzeria : IWindow, IRestaurant {
Object IWindow.GetMenu() { }
Object IRestaurant.GetMenu() { }
//这个GetMenu方法是可选的,与接口无关
public Object GetMenu() { }
}
MarioPizzeria mp = new MarioPizzeria();
//调用GetMenu()
mp.GetMenu();
//调用IWindow.GetMenu()
IWindow window = mp;
window.GetMenu();
//调用IRestaurant.GetMenu()
IRestaurant restaurant = mp;
restaurant.GetMenu();
显式实现的接口无法被派生类调用