《Effective C#》读书笔记——条目23:理解接口方法和虚方法的区别<使用C#表达设计>
实现和覆写虚方法的区别:接口中声明的成员默认不是虚方法。派生类不能覆写基类中实现的接口成员。接口可以被显式实现,这会使针对该类的公有成员隐藏起来。接口与虚方法的概念不同,用法也不同。
改变从基类继承的接口在派生类中的行为
我们来看一个简单的例子:
1 interface IMsg 2 { 3 void Message(); 4 } 5 public class MyClass : IMsg 6 { 7 public void Message() 8 { 9 Console.WriteLine("MyClass"); 10 } 11 } 12 13 public class MyDerivedClass : MyClass 14 { 15 public new void Message() 16 { 17 Console.WriteLine("MyDerivedClass"); 18 } 19 } 20 21 public class EffectiveCSharp 22 { 23 public static void Main(string[] args) 24 { 25 MyDerivedClass d = new MyDerivedClass(); 26 d.Message(); 27 IMsg m = d as IMsg; 28 m.Message(); 29 30 Console.Read(); 31 } 32 }
运行输出:
我们发现,将MyDerivedClass的实例做了转换之后,调用Message()方法变成了该类基类Class的Message()方法——有时候我们常常需要创建接口,然后在基类中实现它们,并且在派生类中更改它们的实现,这时候我们该怎么办呢?这时候有两种办法可供选择。
1.将实现接口的基类中实现的接口成员定义成:virtual,并在派生类中override
1 interface IMsg 2 { 3 void Message(); 4 } 5 public class MyClass : IMsg 6 { 7 public virtual void Message() 8 { 9 Console.WriteLine("MyClass"); 10 } 11 } 12 13 public class MyDerivedClass : MyClass 14 { 15 public override void Message() 16 { 17 Console.WriteLine("MyDerivedClass"); 18 } 19 } 20 21 public class EffectiveCSharp 22 { 23 public static void Main(string[] args) 24 { 25 MyDerivedClass d = new MyDerivedClass(); 26 d.Message(); 27 IMsg m = d as IMsg; 28 m.Message(); 29 30 Console.Read(); 31 } 32 }
运行输出:
2.将实现接口的基类定义成抽象类,并将实现的接口成员定义为抽象成员
我们同时可以将派生类的重写方法定义成密封的防止其派生类再重写该方法:
1 interface IMsg 2 { 3 void Message(); 4 } 5 public abstract class MyClass : IMsg 6 { 7 public abstract void Message(); 8 } 9 10 public class MyDerivedClass : MyClass 11 { 12 public sealed override void Message() 13 { 14 Console.WriteLine("MyDerivedClass"); 15 } 16 } 17 18 public class EffectiveCSharp 19 { 20 public static void Main(string[] args) 21 { 22 MyDerivedClass d = new MyDerivedClass(); 23 d.Message(); 24 IMsg m = d as IMsg; 25 m.Message(); 26 MyClass c = (MyClass)m; 27 c.Message(); 28 Console.Read(); 29 } 30 }
运行输出:
派生类继承基类中接口的实现
其实派生类可以从基类中继承基类对接口的实现,因为派生类可以把该接口的声明成为其契约的一部分,即使它并没有实现任何该接口中成员的实现,只要类的某个公开可访问的方法与接口的签名相匹配,那么契约的条件即可满足,不过这种方法无法使用显示接口实现。例如下面的示例:
1 interface IMsg 2 { 3 void Message(); 4 } 5 public abstract class MyClass : IMsg 6 { 7 public virtual void Message() 8 { 9 Console.WriteLine("MyClass"); 10 } 11 } 12 13 public class MyDerivedClass : MyClass,IMsg 14 { 15 } 16 17 public class EffectiveCSharp 18 { 19 public static void Main(string[] args) 20 { 21 MyDerivedClass d = new MyDerivedClass(); 22 d.Message(); 23 IMsg m = d as IMsg; 24 m.Message(); 25 MyClass c = (MyClass)m; 26 c.Message(); 27 Console.Read(); 28 } 29 }
运行输出:
小节
实现接口拥有的选择要比创建和覆写虚函数多。我们可以为类层次创建密封类(sealed)的实现、虚实现或者抽象实现。我们还可以创建密封的实现,并在实现接口的方法中提供虚方法调用。我们也可以决定派生类应该如何及何时修改基类中实现的接口成员的默认行为。接口不是虚方法,而是一个单独的契约。