First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 2966 评论 :: 301 Trackbacks

本文中所有图示纯为个人理解(参考了Assembly中元数据的存储方式),与真实情况可能有所出入。 图中绿色表示公有方法,红色表示私有方法。

本文将通过以下四个案例来分析C#中的接口究竟是如何工作的。

1、公有方法实现接口方法

尽管C#在定义接口时不用指明接口方法的访问控制方式,但默认接口方法均为public型(这可以从反编译的IL代码中看到)。下面是使用Reflector查看的接口IL代码

.class private interface abstract auto ansi IControl
{
   .method public hidebysig newslot abstract virtual instance void Paint() cil managed
   {
   }
}

实现接口的类需要实现所有接口方法。通常情况下,接口的实现方法也为public型。如下案例:

using System ;
interface IControl 
{
   void Paint();
}
public class EditBox: IControl 
{
   public void Paint() 
   {
      Console.WriteLine("Pain method is called!");
   }
}
class Test 
{
   static void Main() 
   {
      EditBox editbox = new EditBox(); 
      editbox.Paint();
      ((IControl)editbox).Paint();
   }
}

程序的执行结果为:

Pain method is called!
Pain method is called!

接口就好像是关系型数据库中的一对多表,一个接口对应多个接口方法,每个接口方法又对应虚拟方法表(VMT)中的某个公有或私有方法。上面代码在内存中的镜像可由下图描述:

从图中我们可以看到直接对Paint方法的调用以及通过接口对Paint方法的调用。可见通过接口对方法进行调用需要多出一道转换工作,因此执行效率不如直接调用。

2、私有方法不能实现接口方法

如果想将接口方法直接实现为私有方法是办不到的。下面的EditBox的代码中Paint方法没有特殊说明,默认为private,导致代码无法执行:

using System ;
interface IControl 
{
   void Paint();
}
public class EditBox: IControl 
{
   void Paint() 
   {
      Console.WriteLine("Pain method is called!");
   }
   public void ShowPaint()
   {
      this.Paint();
      ((IControl)this).Paint();
   }
}
class Test 
{
   static void Main() 
   {
      EditBox editbox = new EditBox(); 
      editbox.ShowPaint();
   }
}

程序在编译时将显示如下编译错误:““EditBox”不会实现接口成员“IControl.Paint()”。“EditBox.Paint()”或者是静态、非公共的,或者有错误的返回类型。”

为什么会这样呢?如图:

这是由于接口规范中的方法默认的访问权限是public,而类中的默认访问权限是default,也就是说private,因此导致权限范围收缩,两者权限并不相同,所以必须将类的权限调整为public才可以使上面的代码得以执行。

3、实现专门的接口方法(1)

代码如下:

using System ;
interface IControl 
{
   void Paint();
}
public class EditBox: IControl 
{
   void Paint() 
   {
      Console.WriteLine("Pain method is called!");
   }
   void IControl.Paint() 
   {
      Console.WriteLine("IControl.Pain method is called!");
   }
   public void ShowPaint()
   {
      this.Paint();
      ((IControl)this).Paint();
   }
}
class Test 
{
   static void Main() 
   {
      EditBox editbox = new EditBox(); 
      editbox.ShowPaint();
      //editbox.Paint();
      ((IControl)editbox).Paint();
   }
}

EditBox类拥有一私有Paint方法,但这并不是接口方法的实现(上例已经分析过)。EditBox类中还包含了一“void IControl.Paint()”方法, 是该方法复写了接口的Paint方法,该方法是私有的(通过IL代码可以看出)。

注意:“void IControl.Paint()”前不能加任何的修饰限定符号,诸如public、private等,这在C#的语法中是不允许的。该方法反编译得到的IL代码如下:

.class public auto ansi beforefieldinit EditBox
      extends object
      implements IControl
{
      .......
      .method private hidebysig newslot virtual final instance void IControl.Paint() cil managed
      {
            .override IControl::Paint
      }
}

程序运行时内存中的镜像可简化表示为:

程序执行结果如下:

Pain method is called!
IControl.Pain method is called!
IControl.Pain method is called!

我们之所以可以通过((IControl)editbox).Paint()方法访问到代码是因为接口方法Paint是公有的。但是我们不能通过editbox.Paint()方法访问到代码是因为EditBox的Paint方法是私有的。 在EditBox内部,通过ShowPaint方法可以同时访问私有的Paint方法与接口IControl.Paint方法。

4、实现专门的接口方法(2)

如果EditBox中的Pait方法为公有并且同时提供了IControl.Paint方法,程序将是如何运行的呢?代码如下:

using System ;
interface IControl 
{
   void Paint();
}
public class EditBox: IControl 
{
   public void Paint() 
   {
      Console.WriteLine("Pain method is called!");
   }
   void IControl.Paint() 
   {
      Console.WriteLine("IControl.Pain method is called!");
   }
}
class Test 
{
   static void Main() 
   {
      EditBox editbox = new EditBox(); 
      editbox.Paint();
      ((IControl)editbox).Paint();
   }
}

程序执行结果如下:

Pain method is called!
IControl.Pain method is called!

程序执行时内存布局如下:

可见,EditBox中公有的Paint方法并不是接口实现方法,真正的接口实现方法是IControl.Paint,这将导致editbox.Paint()方法与((IControl)editbox).Paint()的执行结果并不一样。

5、结论

接口方法的实现通常是通过类中的公有方法实现的;

在一些特殊情况下(代码隐藏、一个类实现的两个接口具有相同的接口方法等),需要专门实现某个接口的方法。

posted on 2006-04-17 23:57 吕震宇 阅读(10161) 评论(47)  编辑 收藏 所属分类: 面向对象技术

评论

#1楼  2006-04-18 00:06 mapserver      
吕老师,你好,你的文章写的真不错,大学里像你这样的老师真的不多,很多都是混口饭吃,上课就是照着课本读读,考试就给学生画下重点。
  回复  引用  查看    

#2楼 [楼主] 2006-04-18 00:18 吕震宇      
谢谢!毕竟教好书是教师的职责呀:-)
  回复  引用  查看    

吕老师,文章中的图形是用什么工具画出来的啊
  回复  引用    

#4楼  2006-04-18 07:31 维生素C.NET      
不愧是lecture级别的,blog的时候都这么清晰。
  回复  引用  查看    

#5楼  2006-04-18 09:06 winzheng      
解决了我在接口中的一个误区,谢谢
  回复  引用  查看    

#6楼  2006-04-18 09:31 Tony Qu      
吕老师恐怕是我见过的最负责任的老师了,有机会我来上你的课,哈哈,不过我属于问题少年型的,希望吕老师不会害怕
  回复  引用  查看    

#7楼  2006-04-18 09:33 铱星      
写得不错啊

唉,我上学的时候怎么没有碰到你这样的老师呢?
  回复  引用  查看    

#8楼  2006-04-18 09:56 qian_qainjin      
这是 明确实现接口的一种方式。 明确实现接口的方法 ,在调用时 只能通过接口的方式来访问, 这就 好像 实现了接口的私有实现。 文章很入理三分 精彩 !!
  回复  引用  查看    

#9楼  2006-04-18 10:42 Leonic      
学习了,虽然我所知道的也和你说的大致一样,但我没你理解得透彻,正所谓: 知其然不知所以然.
  回复  引用  查看    

#10楼  2006-04-18 10:45 idior      
估计很少有人会用这个方法。

string numStr="1234";
int r=((IConvertible) numStr).ToInt32(null);
WL(r+12);
  回复  引用  查看    

MSDN上写的很明白阿,当你显示的实现接口的方法的时候,你是不能够用class instance去调用的。
  回复  引用    

#12楼 [楼主] 2006-04-18 14:01 吕震宇      
@wigruky'Blog
图是用Excel画的。非常实用的软件,上课的一些演示我也喜欢在Excel中画。:)
  回复  引用  查看    

#13楼 [楼主] 2006-04-18 14:03 吕震宇      
@idior
长见识了,没想到居然也可以如此转换!看来真的要为int.Parse方法的存在鼓掌了。
  回复  引用  查看    

这是显式接口实现。

1. 这个功能不是谁都知道。
2. 这个功能不是谁都明白原理。
3. 这个功能不是谁都穷根究底过。

综上,感谢吕震宇。
  回复  引用    

#15楼  2006-04-18 15:29 BRYANZK [未注册用户]
网上很少能见到象吕老师这样好的人和技术blog啊,吕老师能不能把面向对象模型在内存中的生成和存在方式仔细讲述一下啊,我觉得这个要是可以搞懂的话,很多实际问题都可以迎刃而解啊!!!
  回复  引用    

#16楼 [楼主] 2006-04-18 16:06 吕震宇      
@BRYANZK

谢谢支持!我也正有这个想法。现在正在酝酿下一篇文章《为什么VB.net的Share(共享)方法在C#中叫Static(静态)?》,看似愚蠢的问题,但是其中却蕴涵着面向对象程序设计语言中深层次、本质上的内容。

我也想借这篇文章说说面向对象模型在内存中的生成和存在方式(当然只能算个人理解,实际什么样子很难说,我只能从大量的资料中猜测出内存结构)。写完需要些时间,而且很可能是一个系列的文章。希望能不负所望。
  回复  引用  查看    

#17楼  2006-04-18 16:24 装配脑袋      
竟然还会写和VB有关的文章,期待ing
  回复  引用  查看    

#18楼  2006-04-18 16:29 装配脑袋      
回复本篇文章,C#的显式实现语法其实是经过限制的,本来一个方法override哪个方法,这种关系是可以指定的。
比如C++/CLI中,可以为实现接口的方法换名,并改变访问级别
public: virtual void MyPaint() = IControl::Paint

VB中也允许该用法,但仅限于接口
Protected Overridable Sub Paint() Implements IControl.Paint
  回复  引用  查看    

#19楼 [楼主] 2006-04-18 21:08 吕震宇      
@装配脑袋

呵呵,其实我对VB.net只能算半瓶子醋,而对C++的了解更是几乎为0,我只不过借用一下VB中Share这个概念而已,关键是说说C#面向对象的本质内容(当然纯为个人理解)。
  回复  引用  查看    

#20楼  2006-04-18 22:02 SoulEdge [未注册用户]
这个老师分析得很好。不过单纯从用法上来说,一般真的没有必要这样做。既然实现接口,为什么还要把接口方法隐藏为私有?
  回复  引用    

#21楼  2006-04-19 07:54 装配脑袋      
我也一直疑惑为什么叫static,因为字面上static应该和dynamic对应,但是这里却是和instance对应。
PS. 我觉得VB使用Shared关键字多半是因为Static修饰函数的语法在VB6中有很不相同的意义,为了防止升级用户理解错误,才用Shared代替Static。
  回复  引用  查看    

#22楼  2006-04-19 17:30 idior      
确实是shared更形象一些
  回复  引用  查看    

#23楼 [楼主] 2006-04-19 21:39 吕震宇      
@装配脑袋
@idior

确实是Share更形象一些,然而Static确揭示了本质,这也是我写这篇文章的目的:试图解释static的本质并以此映射面向对象的本质。当然文中除了static方法外,少不了介绍Instance方法(微软教材中叫Object Method,我个人喜欢Dynamic Method这个名字,只是没有人用罢了)。
  回复  引用  查看    

#24楼  2006-04-20 10:07 yuran [未注册用户]
如果同时存在:

public void Paint()
{
Console.WriteLine("Pain method is called!");
}
void IControl.Paint()
{
Console.WriteLine("IControl.Pain method is called!");
}


IControl.Paint这个是实现接口的方法。

如果只有:
public void Paint()
{
Console.WriteLine("Pain method is called!");
}

它就是实现接口的方法吧!
  回复  引用    

#25楼  2006-04-23 17:47 夏林 [未注册用户]
吕老师,谢谢您的无私奉献
  回复  引用    

#26楼  2006-04-26 16:04 yw [未注册用户]
不错,以前就知道接口的作用,没有考虑过接口可以通过私有方式实现,但是,问一下,私有方式实现接口在什么情况下使用?
  回复  引用    

#27楼  2006-05-16 20:33 kun [未注册用户]
非常感谢吕兄的文章。工作以后天天赶项目,平常都是拿别人的东西来做应用,编出来的东西一点儿面对对象的智慧也没有。
回过头来看你的文章重拾这些最基础的东西才让我感觉平静和清澈。
  回复  引用    

吕老师,关注过你的blog已经很久了[主要看你写的设计模式系列]、对你的专业知识和尽职敬业的态度非常钦佩。
大学里面现在能把设计模式列入课程并且能够有好的讲师确实是学生的幸运。
请问吕老师是程序员出身的么?打算始终做教育工作者还是有意成为一个高级的技术人才?
呵呵、一个从业几年的技术爱好者随便问问#_#:)
  回复  引用    

#29楼  2006-05-25 12:09 三月      
谢谢!
有没有带参数实现的办法呢?
例如:
using System ;
interface IControl
{
void Paint();
}
public class EditBox: IControl
{
public void Paint()
{
Console.WriteLine("Pain method is called!");
}
void IControl.Paint()
{
Console.WriteLine("IControl.Pain method is called!");
}
}
class Test
{
static void Main()
{
EditBox editbox = new EditBox();
editbox.Paint();
((IControl)editbox).Paint();
}
}
如果Paint() 是带参数的Paint(string str)
那么 editbox.Paint()可以一样这样带参数吗?editbox.Paint(string str)

  回复  引用  查看    

#30楼  2006-06-03 17:24 roy1 [未注册用户]
猛學習﹐真是好老師﹗
  回复  引用    

#31楼  2006-08-15 12:14 Gaoxiao [未注册用户]
老师太牛了 呵呵 大学很难有这样的好老师 ~~~ 以后有空常来这里学习!~~~
  回复  引用    

#32楼  2006-09-20 15:49 protoss [未注册用户]
我有个问题:
using System ;
interface IControl
{
void Paint();
}
public class EditBox: IControl
{
void Paint()
{
Console.WriteLine("Pain method is called!");
}
void IControl.Paint()
{
Console.WriteLine("IControl.Pain method is called!");
}
public void ShowPaint()
{
this.Paint();
((IControl)this).Paint();
}
}
class Test
{
static void Main()
{
EditBox editbox = new EditBox();
editbox.ShowPaint();
//editbox.Paint();
((IControl)editbox).Paint();
}
}
在这段代码中,既然
void IControl.Paint()
{
Console.WriteLine("IControl.Pain method is called!");
}
是private的为什么((IControl)editbox).Paint();还能起作用,你在文章说是因为在接口中Paint()是public的,那为什么
void Paint()
{
Console.WriteLine("Pain method is called!");
}
这个私有方法不能使用((IControl)editbox).Paint();
调用?这里接口中的Paint()不也是public的吗?
其实我的问题简单点就是
public void Paint()
{
Console.WriteLine("Pain method is called!");
}
void IControl.Paint()
{
Console.WriteLine("IControl.Pain method is called!");
}
这两个私有方法有什么不同。

  回复  引用    

#33楼  2006-09-20 16:05 protoss [未注册用户]
上面倒数第2个方法应该是私有的。
void Paint()
{
Console.WriteLine("Pain method is called!");
}
  回复  引用    

#34楼  2006-10-09 14:47 老陈 [未注册用户]
老吕老师,您可真是高人啊,如果中国大学里的老师能都象您这样,中国的it就有出头之日了
  回复  引用    

#35楼  2006-11-10 17:38 小峰      
回来看看吕老师,呵呵,写的依然是那样精彩。
  回复  引用  查看    

其实大家看看《Inside C#》,就什么都明白了,以上讲的,我觉得跟那本书的第七章讲的差不多,有吵剩饭嫌疑。
  回复  引用    

#37楼  2006-12-24 18:15 kuangye [未注册用户]
这样的规则..不怎么舒服..给编程者的权利太少了...
..
为什么我大学时没有遇到向你这样的老师呢?
  回复  引用    

#38楼  2007-03-15 16:13 白虎      
谢谢吕老师!
  回复  引用  查看    

#39楼  2007-03-28 17:16 Ame      
gooooooooood . studying....
  回复  引用  查看    

#40楼  2007-05-16 12:16 Oriesmap [未注册用户]
我也凑个热闹。
吕老师弄的这个例子很好。也讲的很到位,但是还有一种例外的用法没有讲出来。

public interface IControl
{
void Paint();
}
public class EditBox : IControl
{
public void Paint() {
System.Console.WriteLine("这是继承实现");
}
void IControl.Paint() {
System.Console.WriteLine("这是基类实现");
}
}
class Class1
{
[STAThread]
static void Main(string[] args)
{
IControl ctl = new EditBox();
ctl.Paint();
Console.Read();
}
}
这个时候 结果为“这是基类实现”。

呵呵,顺便问一下吕老师,在IControl ctl = new EditBox();的情况下,如何才能显示 “这是继承实现”?
当然((EditBox)ctl).Paint(); 我是会的。我是想问另外的方法。
  回复  引用    

#41楼 [楼主] 2007-05-23 21:39 吕震宇      
@Oriesmap
我想除了你说的方法外,只能使用反射技术才能实现了,至少我是这么认为的。
  回复  引用  查看    

#42楼  2007-12-27 05:54 张远强      
我改了一改,呵呵:
http://www.cnblogs.com/dnawo/archive/2007/12/27/1016243.html
  回复  引用  查看    

#43楼  2008-02-26 22:45 情缘      
吕老师写的很不错啊!
  回复  引用  查看    

#44楼  2008-08-29 11:55 jay-c      
“EditBox类中还包含了一“void IControl.Paint()”方法, 是该方法复写了接口的Paint方法”----这里你理解错了,void IControl.Paint()不是对接口方法的复写,只是对指定接口方法的调用而已,是显式接口实现,这些在MSDN里面都有详细的介绍。
  回复  引用  查看    

#45楼  2008-08-29 12:00 jay-c      
显式接口主要是应用在一个类继承多个接口的时候,这些接口存在相同的方法,那么在类中调用方法就必须指定接口,否则。。结果可想而知。
public class C : IA,IB
{
void IA.Paint()
{
}
void IB.Paint()
{
}
}
  回复  引用  查看    

#46楼  2008-09-06 14:32 aierong      
good
  回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
博客园首页

新闻频道

社区

小组

博问

网摘

闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-04-18 00:15 编辑过
成果网帮您增加网站收入


相关链接: