接口对一组方法签名进行了统一命名。接口允许定义事件、无参属性、有参属性(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();
显式实现的接口无法被派生类调用