惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Announcements
Recent Announcements
IT之家
IT之家
Google DeepMind News
Google DeepMind News
罗磊的独立博客
爱范儿
爱范儿
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
U
Unit 42
MongoDB | Blog
MongoDB | Blog
S
SegmentFault 最新的问题
B
Blog
博客园 - 叶小钗
月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

博客园 - aiya

Effective C# Item 35: Prefer Overrides to Event Handlers Effective C# Item 40: Match Your Collection to Your Needs 为程序添加自己的跟踪侦听器 Effective C# Item 31: Prefer Small, Simple Functions Effective C# Item 30: Prefer CLS-Compliant Assemblies Chapter 4 Create Binary Components Effective C# Item 29: Use the new Modifier Only When Base Class Updates Mandate it Effective C# Item 28: Avoid Conversion Operators Effective C# Item 27: Avoid ICloneable Effective C# Item 26: Implement Ordering Relations with IComparable and IComparer 阿迪耐斯 Effective C# Item 25: Prefer Serializable Type Effective C# Item 24: Prefer Declarative to Imperative Programming Effective C# Item 23: Avoid Returning References to Internal Class Objects Effective C# Item 22: Define Outgoing Interface With Events 使用GZipStream类压缩和解压文件夹 Effective C# Item 21: Express Callbacks with Delegates Effective C# Item19: Prefer Defining and Implementing Interfaces to Inheritance Effective C# Item18: Implement the Standard Dispose Pattern
Effective C# Item 20: Distinguish Between Implementing In...
aiya · 2006-11-13 · via 博客园 - aiya

Effective C# Item 20: Distinguish Between Implementing Interfaces and Overriding Virtual Functions

      乍看之下,实现接口和重写虚方法是相似的,它们都为一个声明好的成员提供了定义。这种看法是错误的。实现接口和重写虚方法是有很大区别的。在默认情况下,在接口中声明的成员不是虚拟的。派生类不能够重写基类中实现的接口。

      有一种方法可以让派生类修改基类中接口的实现。要实现这一点,我们需要为派生类创建一个钩子(hook)。

      下面我们举一个例子,一个简单的接口和一个类对接口的实现如下:

interface IMsg
{
      
void Message();
}

public class MyClass : IMsg
{
      
public void Message()
      
{
            Console.WriteLine(
"MyClass");
      }

}

      Message()方法是MyClass类的公共接口中的一部分。我们也可以通过IMsg接口来访问MyClass类型中的成员。现在我门来让派生类中的情况稍微复杂一些:

public class MyDerivedClass : MyClass
{
      
public new void Message()
      
{
            Console.WriteLine(
"MyDerivedClass");
      }

}

      注意到我们在这里使用了new关键字来定义之前已经存在的Message()方法。MyClass.Message()方法不是虚方法,派生类是不能对它进行重写的。这里派生类创建了一个新的Message(),而不是重写了基类中的方法。因此,我们还是可以通过IMsg接口来访问到基类中的Message()方法。

MyDerivedClass d = new MyDerivedClass();
d.Message();           
//结果为"MyDerivedClass"
IMsg m = d as IMsg;
m.Message();           
//结果为"MyClass"

      接口方法不是虚拟的,当我们实现接口时,就是为我们的类声明了一个有特殊含义的具体实现。

      但是有时候我们希望创建接口,在基类中实现,并且在派生类中修改它们的行为。这是可以做到的。我们有两种选择,如果我们的基类是不可达的,那么我们可以在派生类中重新实现该接口:

public class MyDerivedClass : MyClass, IMsg
{
      
public new void Message()
      
{
            Console.WriteLine(
"MyDerivedClass");
      }

}

      额外添加的IMsg改变了我们派生类的行为。现在IMsg.Message()使用的将是派生类中实现的版本。

MyDerivedClass d = new MyDerivedClass();
d.Message();           
//结果为"MyDerivedClass"
IMsg m = d as IMsg;
m.Message();           
//结果为"MyDerivedClass"

      我们依然需要使用new关键字来声明MyDerivedClass.Message()方法。但是这仍然存在问题。通过基类的引用我们仍然会得到基类中的实现版本。

      解决这个问题的唯一方法是修改基类,将这个接口方法声明为虚拟的。

public class MyClass : IMsg
{
      
public virtual void Message()
      
{
            Console.WriteLine(
"MyClass");
      }

}

      这样所有从MyClass派生的类都可以声明它们自己的Message()了。不论是通过派生类,接口还是基类,调用的始终会是重写的版本。

      如果我们不喜欢这种混乱的虚拟函数,可以稍微对基类作些修改:

public abstract class MyClass : IMsg
{
      
public virtual void Message();
}

      现在我们可以实现一个接口而不完全实现其中的方法。因为将方法声明为抽象的,我们必须在派生类中提供它的具体实现。IMsg是MyClass声明中的一部分,但是其中方法的具体定义却转移到派生类中实现。

      实现接口比创建和重写虚方法有更多的选择。我们可以将实现声明为密封的,虚拟的,或者抽象的。我们可以根据具体情况来决定派生类是否可以修改基类中对接口的默认实现。

      译自   Effective C#:50 Specific Ways to Improve Your C#                      Bill Wagner著

      回到目录