接口

接口只是对一组方法签名进行了统一命名,这些方法不提供任何实现。类通过指定接口名称来继承接口,而且必须显示实现接口方法,否则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(); //会装箱
原文地址:https://www.cnblogs.com/mingjie-c/p/11674120.html