• Effective C# Item20:明辨接口实现和虚方法重写


        接口实现和虚方法重写看上去很像,它们都可以对定义在另一个类型中的方法进行重新实现,但是,这两种方式有很大的区别。

        我们来看下面的代码,首先定义一个接口。

    1 public interface InterfaceA
    2 {
    3 void MethodA();
    4 }

        我们可以采取以下三种方式来实现上述接口InterfaceA。

    代码
    1 /// <summary>
    2 /// the first way to implement interface, and this is the most common way.
    3 /// </summary>
    4   public class BaseClassWithoutVirtual : InterfaceA
    5 {
    6 public void MethodA()
    7 {
    8 Console.WriteLine("BaseClassWithoutVirtual");
    9 }
    10 }
    11
    12 /// <summary>
    13 /// the second way to implement interface, define method as virtual.
    14 /// </summary>
    15   public class BaseClassWithVirtual : InterfaceA
    16 {
    17 public virtual void MethodA()
    18 {
    19 Console.WriteLine("BaseClassWithVirtual");
    20 }
    21 }
    22
    23 /// <summary>
    24 /// the third way to implement interface, define class and method as abstract.
    25 /// </summary>
    26   public abstract class BaseClassWithAbstract : InterfaceA
    27 {
    28 public abstract void MethodA();
    29 }

        上面代码中,使用三种方式实现了接口,其中第一种方式是我们最常见的,定义一个类去实现接口,然后分别实现接口中定义的每一个方法、属性、事件或者索引器;第二种方式一般是在编写框架时使用,它在实现接口定义的方法时,将方法声明为virtual,这样子类可以重写这个方法;第三种方式一般也是在编写框架时使用,它将实现接口的类以及接口中的每个方法都声明为abstract,这种类型是不可以实例化的,并且它的所有派生类都必须实现接口中定义的方法。

        下面的代码展示了如何创建上述类的派生类。

    代码
    1 public class SubClassWithoutVirtual : BaseClassWithoutVirtual
    2 {
    3 public new void MethodA()
    4 {
    5 Console.WriteLine("SubClassWithoutVirtual");
    6 }
    7 }
    8
    9 public class SubClassWithoutVritual2 : BaseClassWithoutVirtual, InterfaceA
    10 {
    11 public new void MethodA()
    12 {
    13 Console.WriteLine("SubClassWithoutVritual2");
    14 }
    15 }
    16
    17 public class SubClassWithVirtual : BaseClassWithVirtual
    18 {
    19 public override void MethodA()
    20 {
    21 Console.WriteLine("SubClassWithVirtual");
    22 }
    23 }
    24
    25 public class SubClassWithAbstract : BaseClassWithAbstract
    26 {
    27 public override void MethodA()
    28 {
    29 Console.WriteLine("SubClassWithAbstract");
    30 }
    31 }

        我们来看下面的代码,是对前面提到的所有代码的测试方法。

    代码
    1 private static void Test()
    2 {
    3 Console.WriteLine("Show Base Class Info");
    4 BaseClassWithoutVirtual baseClass1 = new BaseClassWithoutVirtual();
    5 baseClass1.MethodA();
    6 BaseClassWithVirtual baseClass2 = new BaseClassWithVirtual();
    7 baseClass2.MethodA();
    8 Console.WriteLine();
    9 Console.WriteLine("Show Sub Class Info Without Virtual");
    10 SubClassWithoutVirtual subClass1 = new SubClassWithoutVirtual();
    11 subClass1.MethodA();
    12 if (subClass1 is InterfaceA)
    13 {
    14 (subClass1 as InterfaceA).MethodA();
    15 }
    16 if (subClass1 is BaseClassWithoutVirtual)
    17 {
    18 (subClass1 as BaseClassWithoutVirtual).MethodA();
    19 }
    20 SubClassWithoutVritual2 subClass2 = new SubClassWithoutVritual2();
    21 subClass2.MethodA();
    22 if (subClass2 is InterfaceA)
    23 {
    24 (subClass2 as InterfaceA).MethodA();
    25 }
    26 if (subClass2 is BaseClassWithoutVirtual)
    27 {
    28 (subClass2 as BaseClassWithoutVirtual).MethodA();
    29 }
    30
    31 Console.WriteLine();
    32 Console.WriteLine("Show Sub Class Info With Virtual");
    33 SubClassWithVirtual subClass3 = new SubClassWithVirtual();
    34 subClass3.MethodA();
    35 if (subClass3 is InterfaceA)
    36 {
    37 (subClass3 as InterfaceA).MethodA();
    38 }
    39 if (subClass3 is BaseClassWithVirtual)
    40 {
    41 (subClass3 as BaseClassWithVirtual).MethodA();
    42 }
    43
    44 Console.WriteLine();
    45 Console.WriteLine("Show Sub Class Info With Abstract");
    46 SubClassWithAbstract subClass4 = new SubClassWithAbstract();
    47 subClass4.MethodA();
    48 if (subClass4 is InterfaceA)
    49 {
    50 (subClass4 as InterfaceA).MethodA();
    51 }
    52 if (subClass4 is BaseClassWithAbstract)
    53 {
    54 (subClass4 as BaseClassWithAbstract).MethodA();
    55 }
    56 }

        上述代码的运行结果如下图所示。

        关于测试方法,首先,会打印出基类的信息,然后针对每种基类,分别打印派生类的信息,其中,对于每一种派生类,在打印信息时,分为三种情况:1)按照派生类的类型输出;2)按照基类的类型输出;3)按照接口的类型输出。

        我们可以看到对于将基类中方法声明为virtual或者abstract的方式来说,按照派生类的类型和按照基类的类型输出的结果是一致的。

        但是对于最普通的实现接口方式来说,我们定义了两个派生类,第一个派生类直接继承基类,然后通过new关键字重新声明MehtodA方法;第二个派生类直接继承基类,并且实现接口InterfaceA,这个派生类同样也以new关键字重新声明了MethodA方法。但是在执行过程中,对于第一个派生类,将其转换为基类类型或者接口类型后,都会调用基类中的方法;而第二个派生类,在将其转换为基类类型后,会调用基类中的方法,将其转换为接口类型后,就会调用派生类中的方法。

        对于出现上面的结果,如果我们能从对象的运行时类型去考虑,就会发现很容易理解。

        总结:实现接口拥有的选择要比创建和重写虚函数多。我们可以为类层次创建密封的实现、虚实现或者抽象的合约。我们也可以决定派生类如何以及何时修改“基类中实现的接口方法的默认行为”。接口方法不是虚方法,而是一个单独的合约。

  • 相关阅读:
    js正则表达式验证【引用网址】
    Chart控件的使用实例
    C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解
    C#进阶系列——WebApi 接口参数不再困惑:传参详解
    C#进阶系列——WebApi 路由机制剖析:你准备好了吗?
    【UiPath 中文教程】02
    八幅漫画理解使用JSON Web Token设计单点登录系统
    JSON Web Token(缩写 JWT) 目前最流行的跨域认证解决方案
    webservice 教程
    IBM MQ 使用指南
  • 原文地址:https://www.cnblogs.com/wing011203/p/1649488.html
Copyright © 2020-2023  润新知