《CLR via C#》设计类型.接口

13.接口

13.1定义接口

1.接口只是对一组方法签名进行了统一命名,并且这些方法不提供任何实现。
2.引用类型和值类型都可以定义零或多个接口,但值类型的实例在转换为接口时必须装箱。
2.类型继承的接口里的所有方法都必须在该类型里实现。
3.在接口里,可以定义方法、事件、无参属性和索引器。但不能定义任何构造器和任何实例字段,也不能定义静态方法、静态字段、常量和静态构造器这几个静态成员。
4.接口定义可从另一个或多个接口“继承”,这里的“继承”不一样,可以理解为:将其他接口的协定包括到新接口中。包括其他接口的协定的新接口也不允许实现其他接口里的方法,但继承这个新接口的类型就必须要实现该接口和该接口所“继承”的其他接口里的全部方法,举例:

public interface IEnumerable<out T> : IEnumerable
public interface ICollection<T> : IEnumerable<T>, IEnumerable
//ICollection<T>接口包括了IEnumerable<T>和IEnumerable接口的协定,继承ICollection<T>接口的类型,必
// 须实现ICollection<T>、IEnumerable<T>和IEnumerable这三个接口所定义的方法。
13.2继承接口

如何定义实现了接口的类型,举例:

public static void Go()
{
    Point[] points = new Point[] { new Point(3, 3), new Point(1, 2) };
    if (points[0].CompareTo(points[1]) > 0)
    {
        Point temp = points[0];
        points[0] = points[1];
        points[1] = temp;
    }
    Console.WriteLine("from closest to far:");
    foreach (var p in points)
        Console.WriteLine(p);
}

int mX, mY;
public Point(int x, int y) 
{
    mX = x; mY = y;
}
// 该方法实现IComparable<T>里的CompareTo方法,实现接口的方法必须标记为public
public int CompareTo(Point other) 
{
    return Math.Sign(Math.Sqrt(mX * mX + mY * mY) - Math.Sqrt(other.mX * other.mX + 
    	other.mY * other.mY));
}
public override string ToString()
{
    return string.Format("{0}, {1}", mX, mY);
}

输出:

from closest to far:
1, 2
3, 3

另外,CLR规定:如果不主动把接口方法标记为virtual,编译器会将它标记为virtual和sealed,这会阻止该类型的派生类重写该方法;如果主动把接口方法标记为virtual,编译器会将该方法标记为virtual,使派生类能重写它。

13.3调用接口方法的更多探讨

举例:String类型不仅继承了Object类型,还继承了几个接口:public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<String>, IEnumerable<char>, IEquatable<String>。这意味着String类型不必要实现Object类型里的方法,但必须实现这几个接口里的所有方法。
另外,可以用接口类型定义变量(字段、参数或局部变量),使用接口类型的变量可以调用该接口定义的方法,举例:

public static void Go()
{
    string s= "jump";
    // s可以调用Object类型和String继承的几个接口里定义的所有方法

    ICloneable cloneable = s; // cloneable对象和s引用同一个String对象
    // 而cloneable只能调用Object类型和ICloneable接口里定义的所有方法
}
13.4泛型接口

前一章已经讲了泛型接口,这里说一下泛型接口的三个好处:
1.编译时类型安全;
举例:

public static void Method1()
{
    int x = 1, y = 2;
    IComparable c = x; // 装箱(用ILDasm可以看到有没有装箱),因为接口是引用类型
    c.CompareTo(y); // 装箱,因为CompareTo(object obj); 所以CompareTo期待一个(实际类型为Int32的)Object
    //c.CompareTo("jump");// 编译时通过,运行时报错:System.ArgumentException:“对象的类型必须是 Int32。”
}
public static void Method2()
{
    int x = 1, y = 2;
    IComparable<int> c = x; // 装箱,因为接口是引用类型
    c.CompareTo(y); // 不装箱,因为CompareTo期待一个Int32
    //c.CompareTo("jump"); // 编译时就报错:无法从string转换为int
}

2.处理值类型时发生装箱的次数减少很多,如上例;
3.一个类型可以实现同一个接口若干次,只要类型参数不同就行。

补充:FCL为什么会定义IComparable、ICollection、IList等非泛型接口版本,还会定义IComparable<in T>、ICollection<T>、IList<T>等泛型接口版本?因为FCL保留非泛型版本是为了向后兼容,现在一般使用泛型版本就好。

13.5显示接口方法实现(简称EIMI)

EIMI主要用于实现“某类型继承的多个接口都具有相同名称和签名的方法”的情况,举例:

public sealed class EIMIFunc
{
    public interface IRestaurant { Object GetMenu(); }
    public interface IWindow { Object GetMenu(); }
    public sealed class Test : IRestaurant, IWindow
    {
        // 显示接口方法EIMI的实现格式
        Object IRestaurant.GetMenu() { return null; } 
        Object IWindow.GetMenu() { return null; }

        // 该方法与这两个接口没有任何关系,是可选的,只是名字巧合而已
        public Object GetMenu() { return null; }
    }
    public static void Go()
    {
        Test test = new Test();

        // 显示接口方法EIMI的调用格式
        IRestaurant ir = test;
        ir.GetMenu();
        IWindow iw = test;
        iw.GetMenu();

        // 调用一个与这两个接口没有任何关系的方法
        test.GetMenu();
    }
}

EIMI主要用于这种情况,尽量避免在其他情况上使用EIMI。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值