zoukankan      html  css  js  c++  java
  • 接口

    接口只是对一组方法签名进行了统一命名,这些方法不提供任何实现。类通过指定接口名称来继承接口,而且必须显示实现接口方法,否则CLR 会认为此类型定义无效。

    类继承有一个重要的特点,凡是能使用基类型实例的地方,都能使用派生类型的实例。类似地,接口继承的一个重要特点是,凡是能使用具名接口的类型的实例的地方,都能使用实现了接口的一个类型的实例。

     

    继承接口

    C# 编译器要求将实现接口的方法标记为 public。CLR 要求将接口方法标记为virtual。

    不将方法显示标记为 virtual,编译器会将它们标记为virtual和sealed:这会阻止派生类重写接口方法。

    将方法显示标记为 virtual,编译器就会将该方法标记为virtual,使派生类能重写它。

    派生类不能重写sealed 接口方法。但派生类可重新继承同一个接口,并为接口方法提供自己的实现。在对象上调用接口方法时,调用的是方法在该对象类型中的实现。

    using System;
    public static class Program {
        public static void Main() {
            /************************* First Example *************************/
            Base b = new Base();
            // Calls Dispose by using b's type: "Base's Dispose"
            b.Dispose();
            // Calls Dispose by using b's object's type: "Base's Dispose"
            ((IDisposable)b).Dispose();
            /************************* Second Example ************************/
            Derived d = new Derived();
            // Calls Dispose by using d's type: "Derived's Dispose"
            d.Dispose();
            // Calls Dispose by using d's object's type: "Derived's Dispose"
            ((IDisposable)d).Dispose();300 PART II Designing Types
            /************************* Third Example *************************/
            b = new Derived();
            // Calls Dispose by using b's type: "Base's Dispose"
            b.Dispose();
            // Calls 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 reimplements 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();
        }
    }

    隐式和显式接口方法实现(幕后发生的事情)

    类型加载到CLR 中时,会为该类型创建并初始化一个方法表。在这个方法表中,类型引入的每个新方法都有对应的记录项:另外,还为该类型继承的所有虚方法添加了记录项。继承的虚方法既有继承层次结构中的各个基类型定义的,也有接口类型定义的。所以对于下面这个简单的类型定义:

    internal sealed class SimpleType : IDisposable{
    public void Dispose()
    {
    Console.WriteLine("Dispose");
    }
    }

    该类型的方法表将包含以下方法的记录项。

    • object (隐式继承的基类)定义的所有虚实例方法。

    • IDisposable(继承的接口)定义的所有接口方法。本例只有一个方法,即Dispose ,因为IDisposable 接口只定义了这个方法。

    • SimpleType 引入的新方法 Dispose。

     

    C# 编译器将新方法和接口方法匹配起来之后,会生成元数据,指明 SimpleType 类型的方法表中的两个记录项引用同一个实现。为了更清楚的理解这一点,下面的代码演示来了如何调用类的公共Dispose 方法以及如何调用 IDisposable 的Dispose 方法在类中的实现:

    public sealed class Program{
       public static void Main(){
           SimpleType st = new SimpleType();
           //调用公共的Dispose 方法实现,调用的是SimpleType定义的Dispose方法。
           st.Dispose();
           
           //调用IDisposable 的Dispose 方法的实现
           IDisposable d = st;
           d.Dispose();
      }
    }    

    由于C# 要求公共Dispose 方法同时是 IDisposable 的Dispose 方法的实现,所以会执行相同的代码。

    现在重写SimpleType ,以便于看出区别:

    internal sealed class SimpleType : IDisposable{
    public void Dispose() {Console.WriteLine("public Dispose");}
    void IDisposable.Dispose() {Console.WriteLine("IDisposable Dispose");}
    }

    在不改动前面的Main 方法的前提下,重新编译并再次运行程序,输出结果如下所示:

    public Dispose
    IDisposable Dispose

    在C# 中,将定义方法的那个接口的名称作为方法名前缀(例如 IDisposable.Dispose),就会创建显示接口方法实现(Explicit Interface Method Implementation, EIMI)。注意,C#中不允许在定义显示接口方法时指定可访问性。编译器生成方法的元数据时,可访问性会自动设为private,防止其他代码在使用类的实例时直接调用接口方法。只有通过接口类型的变量才能调用接口的方法。

    还要注意,EIMI 方法不能标记为 virtual ,所以不能被重写。这是由于 EIMI方法并非真的是类型的对象模型的一部分,他只是将接口和类型连接起来,同时避免公开行为/方法。

     

    如果两个接口定义了具有相同名称和签名的方法。如下所示:

    public interface IWindow{
    Object GetMenu();
    }
    public interface IRestaurant{
    Object GetMenu();
    }
    // This type is derived from System.Object and
    // implements the IWindow and IRestaurant interfaces.
    public sealed class MarioPizzeria : IWindow, IRestaurant {
       // This is the implementation for IWindow's GetMenu method.
       Object IWindow.GetMenu() { ... }
       // This is the implementation for IRestaurant's GetMenu method.
       Object IRestaurant.GetMenu() { ... }
       // This (optional method) is a GetMenu method that has nothing
       // to do with an interface.
       public Object GetMenu() { ... }
    }

    由于这个类型实现多个接口的GetMenu方法,所以要告诉C# 编译器每个GetMenu方法对应的是哪个接口的实现。

    MarioPizzeria mp = new MarioPizzeria();
    // This line calls MarioPizzeria's public GetMenu method
    mp.GetMenu();
    // These lines call MarioPizzeria's IWindow.GetMenu method
    IWindow window = mp;
    window.GetMenu();
    // These lines call MarioPizzeria's IRestaurant.GetMenu method
    IRestaurant restaurant = mp;
    restaurant.GetMenu()

     

    小结:

    • 接口于class 类似,但是它只为其成员提供了规格,而没有提供具体实现

    • 接口的成员都是隐式抽象的,

    • 接口的成员都是隐式public 的,不可以声明访问修饰符

    • 实现接口对它的所有成员进行public实现

    • 一个class 或者 struct 可以实现多个接口

    • 对象和接口转换,可以隐式的把一个对象转换成它实现的接口

    • 接口可以继承其他接口

    • 实现多个接口时,可能造成方法签名的冲突,这时需要显示的继承接口

      • 显示实现的方法不是public的,在调用时应该转成对应的接口再调用

    • 隐式实现的接口成员默认是 sealed,不可继承

      • 如果想进行重写的化,必须在基类中把成员标记为virtual 或者 abstract。

      • 如果父类不标记virtual,子类会重写该方法,例如:

        Child c = new Child();
        c.Do(); // 调用的是子类实现的接口方法
        ((Parent)c).Do(); //这样写才会调用父类实现的方法(假如child 继承了Parent,Parent又实现了接口)
        ((IDo)c).Do(); //这样写还是会调用父类实现的方法,因为父类是对接口的直接实现
      • 如果父类标记virtual,子类要标记override 重写该方法,还是上面的三个输出,会得到下面的结果:三个都是调用子类的方法。

        Child c = new Child();
        c.Do(); // 调用的是子类实现的接口方法
        ((Parent)c).Do(); //会调用子类实现的方法,相当于覆盖了父类的,父类的方法不存在了。
        ((IDo)c).Do(); //还是调用子类的实现方法

        Parent p = new Parent();
        p.Do();
        ((IFoo)p).Do(); //这里调用的是父类的方法
    • 显示实现的接口成员不可以标记为virtual,也不可以通过寻常的方式来重写,但是可以对其重新实现。

      • 子类可以重新实现父类已经实现的接口成员。

      • 重新实现会"劫持" 成员的实现(通过转化为接口然后调用)

      public class Parent: IDo
      {
      public void IDo.Do() => Console.WriteLine("Parent"); //显示实现
      }

      public class Child:Parent, IDo
      {
      public void Do() => Console.WriteLine("Child");
      }

      class Program
      {
      static void main(string[] args)
      {
      Child c = new Child();
      c.Do();
      ((IDo)c).Do(); //这里调用子类方法
      ((Parent)c).Do(); //这里会报错
      }
      }
    • 重新实现接口的替代方案

      • 隐式实现成员的时候,按需标记virtual

      • 显示实现的成员的时候这样做:

        public class TestBox : IUndoable
        {
        void IUndoable.Undo() => Undo();
        protected virtual void Undo() => Console.WriteLine("TextBox.Undo");
        }

        public class RichTextBox:TextBox
        {
        protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
        }
      • 如果不想又子类,直接把class 给 sealed。

    • 把struct 转化为接口会导致装箱,

      • 调用struct 上隐式实现的成员不会导致装箱

      interface I {void Foo();}
      struct S : I { public void Foo() {}}

      S s= new S();
      s.Foo(); //不会装箱

      I i = s;
      i.Foo(); //会装箱
  • 相关阅读:
    zabbix linux 客户端编译安装
    yum安装grafana
    zabbix 安装和配置
    安装PHP
    wrk压力测试
    新加坡地图
    shell中去除变量去除所有空格或者去除变量首尾空格的常用几种方法
    新鲜出炉!春招-面试-阿里钉钉、头条广告,美团面经分享,看我如何拿下offer!
    面试阿里,字节跳动90%会被问到的Java异常面试题集,史上最全系列!
    最新出炉,字节跳动一二三面面经,看我如何一步一步攻克面试官?
  • 原文地址:https://www.cnblogs.com/mingjie-c/p/11674120.html
Copyright © 2011-2022 走看看