Effective C# Item20:明辨接口实现和虚方法重写
2014-07-25 09:08
357 查看
接口实现和虚方法重写看上去很像,它们都可以对定义在另一个类型中的方法进行重新实现,但是,这两种方式有很大的区别。
我们来看下面的代码,首先定义一个接口。
我们可以采取以下三种方式来实现上述接口InterfaceA。
上面代码中,使用三种方式实现了接口,其中第一种方式是我们最常见的,定义一个类去实现接口,然后分别实现接口中定义的每一个方法、属性、事件或者索引器;第二种方式一般是在编写框架时使用,它在实现接口定义的方法时,将方法声明为virtual,这样子类可以重写这个方法;第三种方式一般也是在编写框架时使用,它将实现接口的类以及接口中的每个方法都声明为abstract,这种类型是不可以实例化的,并且它的所有派生类都必须实现接口中定义的方法。
下面的代码展示了如何创建上述类的派生类。
我们来看下面的代码,是对前面提到的所有代码的测试方法。
上述代码的运行结果如下图所示。
关于测试方法,首先,会打印出基类的信息,然后针对每种基类,分别打印派生类的信息,其中,对于每一种派生类,在打印信息时,分为三种情况:1)按照派生类的类型输出;2)按照基类的类型输出;3)按照接口的类型输出。
我们可以看到对于将基类中方法声明为virtual或者abstract的方式来说,按照派生类的类型和按照基类的类型输出的结果是一致的。
但是对于最普通的实现接口方式来说,我们定义了两个派生类,第一个派生类直接继承基类,然后通过new关键字重新声明MehtodA方法;第二个派生类直接继承基类,并且实现接口InterfaceA,这个派生类同样也以new关键字重新声明了MethodA方法。但是在执行过程中,对于第一个派生类,将其转换为基类类型或者接口类型后,都会调用基类中的方法;而第二个派生类,在将其转换为基类类型后,会调用基类中的方法,将其转换为接口类型后,就会调用派生类中的方法。
对于出现上面的结果,如果我们能从对象的运行时类型去考虑,就会发现很容易理解。
总结:实现接口拥有的选择要比创建和重写虚函数多。我们可以为类层次创建密封的实现、虚实现或者抽象的合约。我们也可以决定派生类如何以及何时修改“基类中实现的接口方法的默认行为”。接口方法不是虚方法,而是一个单独的合约。
我们来看下面的代码,首先定义一个接口。
public interface InterfaceA { void MethodA(); }
我们可以采取以下三种方式来实现上述接口InterfaceA。
/// <summary> /// the first way to implement interface, and this is the most common way. /// </summary> public class BaseClassWithoutVirtual : InterfaceA { public void MethodA() { Console.WriteLine("BaseClassWithoutVirtual"); } } /// <summary> /// the second way to implement interface, define method as virtual. /// </summary> public class BaseClassWithVirtual : InterfaceA { public virtual void MethodA() { Console.WriteLine("BaseClassWithVirtual"); } } /// <summary> /// the third way to implement interface, define class and method as abstract. /// </summary> public abstract class BaseClassWithAbstract : InterfaceA { public abstract void MethodA(); }
上面代码中,使用三种方式实现了接口,其中第一种方式是我们最常见的,定义一个类去实现接口,然后分别实现接口中定义的每一个方法、属性、事件或者索引器;第二种方式一般是在编写框架时使用,它在实现接口定义的方法时,将方法声明为virtual,这样子类可以重写这个方法;第三种方式一般也是在编写框架时使用,它将实现接口的类以及接口中的每个方法都声明为abstract,这种类型是不可以实例化的,并且它的所有派生类都必须实现接口中定义的方法。
下面的代码展示了如何创建上述类的派生类。
public class SubClassWithoutVirtual : BaseClassWithoutVirtual { public new void MethodA() { Console.WriteLine("SubClassWithoutVirtual"); } } public class SubClassWithoutVritual2 : BaseClassWithoutVirtual, InterfaceA { public new void MethodA() { Console.WriteLine("SubClassWithoutVritual2"); } } public class SubClassWithVirtual : BaseClassWithVirtual { public override void MethodA() { Console.WriteLine("SubClassWithVirtual"); } } public class SubClassWithAbstract : BaseClassWithAbstract { public override void MethodA() { Console.WriteLine("SubClassWithAbstract"); } }
我们来看下面的代码,是对前面提到的所有代码的测试方法。
private static void Test() { Console.WriteLine("Show Base Class Info"); BaseClassWithoutVirtual baseClass1 = new BaseClassWithoutVirtual(); baseClass1.MethodA(); BaseClassWithVirtual baseClass2 = new BaseClassWithVirtual(); baseClass2.MethodA(); Console.WriteLine(); Console.WriteLine("Show Sub Class Info Without Virtual"); SubClassWithoutVirtual subClass1 = new SubClassWithoutVirtual(); subClass1.MethodA(); if (subClass1 is InterfaceA) { (subClass1 as InterfaceA).MethodA(); } if (subClass1 is BaseClassWithoutVirtual) { (subClass1 as BaseClassWithoutVirtual).MethodA(); } SubClassWithoutVritual2 subClass2 = new SubClassWithoutVritual2(); subClass2.MethodA(); if (subClass2 is InterfaceA) { (subClass2 as InterfaceA).MethodA(); } if (subClass2 is BaseClassWithoutVirtual) { (subClass2 as BaseClassWithoutVirtual).MethodA(); } Console.WriteLine(); Console.WriteLine("Show Sub Class Info With Virtual"); SubClassWithVirtual subClass3 = new SubClassWithVirtual(); subClass3.MethodA(); if (subClass3 is InterfaceA) { (subClass3 as InterfaceA).MethodA(); } if (subClass3 is BaseClassWithVirtual) { (subClass3 as BaseClassWithVirtual).MethodA(); } Console.WriteLine(); Console.WriteLine("Show Sub Class Info With Abstract"); SubClassWithAbstract subClass4 = new SubClassWithAbstract(); subClass4.MethodA(); if (subClass4 is InterfaceA) { (subClass4 as InterfaceA).MethodA(); } if (subClass4 is BaseClassWithAbstract) { (subClass4 as BaseClassWithAbstract).MethodA(); } }
上述代码的运行结果如下图所示。
关于测试方法,首先,会打印出基类的信息,然后针对每种基类,分别打印派生类的信息,其中,对于每一种派生类,在打印信息时,分为三种情况:1)按照派生类的类型输出;2)按照基类的类型输出;3)按照接口的类型输出。
我们可以看到对于将基类中方法声明为virtual或者abstract的方式来说,按照派生类的类型和按照基类的类型输出的结果是一致的。
但是对于最普通的实现接口方式来说,我们定义了两个派生类,第一个派生类直接继承基类,然后通过new关键字重新声明MehtodA方法;第二个派生类直接继承基类,并且实现接口InterfaceA,这个派生类同样也以new关键字重新声明了MethodA方法。但是在执行过程中,对于第一个派生类,将其转换为基类类型或者接口类型后,都会调用基类中的方法;而第二个派生类,在将其转换为基类类型后,会调用基类中的方法,将其转换为接口类型后,就会调用派生类中的方法。
对于出现上面的结果,如果我们能从对象的运行时类型去考虑,就会发现很容易理解。
总结:实现接口拥有的选择要比创建和重写虚函数多。我们可以为类层次创建密封的实现、虚实现或者抽象的合约。我们也可以决定派生类如何以及何时修改“基类中实现的接口方法的默认行为”。接口方法不是虚方法,而是一个单独的合约。
相关文章推荐
- Effective C# Item20:明辨接口实现和虚方法重写
- [C#]简单重写IComparer接口,实现自己的String.CompareTo 方法,自定义比较规则。
- More Effective C# Item7 : 不要为基类或者接口创建泛型的特殊实现
- Effective C# Item 20: Distinguish Between Implementing Interfaces and Overriding Virtual Functions
- 在C#中使用虚方法、重写方法和抽象方法实现表达式运算
- C# IFormattable接口,实现自定义的字符串格式化方法
- [翻译] Effective C++, 3rd Edition, Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)(上)
- More Effective C# Item6 : 使用委托定义类型参数上的方法约束
- Effective C# 原则20:明辨接口实现和虚函数重载的区别(译)
- C#中继承实现父类方法、重写、重载
- C#中显/隐式实现接口及其访问方法
- [原创]详述IComparer,IComparable接口,实现自定义方法比较对象大小并排序(C#)
- C#中实现接口的几种方法
- C#.net ListView item 拖动排序实现方法
- C#.net ListView item 拖动排序实现方法
- [翻译] Effective C++, 3rd Edition, Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)(下)
- c# 重载WndProc,实现重写“最小化”的方法
- ID的生成策略(hibernate的id生成策略,主键类为什么需要实现序列化接口,同时还要重写hashCode()和equals()方法)
- Effective Java Item3:使用私有构造方法或者枚举类型实现单例
- More Effective C# Item8 :尽可能使用泛型方法,除非需要将类型参数用于实例的字段中