路漫漫,求索不息

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

最近在园子里闲逛看到一篇文章“(抽象)类和接口细节分析”,尽管作者很细心很细致。可事实上C#里面的interface没那么简单,interface有着大量不为人知的小秘密的说。

 

1、值类型也能实现接口。

尽管可能很多人连值类型都没用过,但值类型可以实现接口,是一个非常有用的特性。当值类型转换为接口类型时,会自动装箱成引用类型从而实现多态,但一般用值类型实现接口的老鸟都不会被这些小陷阱所迷惑的。

 

2、除了接口,没有什么类型可以不实现接口

抽象类可以不实现接口?其实不然,抽象类只能抽象接口的实现,即将实现变为abstract的,但不能不实现接口。也许你会疑惑,abstract实现接口成员和不实现接口成员到底有区别?尽管区别不大,但还是存在的,从理论上来说的话,就是slot上的方法的区别。抽象类用abstract成员实现接口后,子类再override来实现抽象类的abstract成员,这会使得子类的实现是在abstract实现之下而不是直接在接口成员之下。从实际效果上来说,由于抽象类的abstract的实现存在,使得子类中可以选择对抽象类和接口进行分别实现(重新实现接口)。

 

3、但事实上接口也不能实现接口,只是把多个接口捆绑起来。

一段有趣的代码如下

public interface IA
{
  void Test();
}

public interface IB : IA { }

public interface IC : IA { }

public interface ID : IB, IC { }

当实现ID时,等于同时实现ID、IC、IB和IA,但事实上,你只需要实现一个Test方法,也只能实现一个Test方法,也就是IA.Test,IB、IC、ID都是没有Test方法的。

当然,你可以尝试在IB或是其他接口中再放入一个Test,便会产生一些奇妙的事情。

 

4、同一接口成员在同一类型中只能被实现一次。

 显示实现接口已经不是什么秘密了,但很多人却并不知道,如果你显示实现了接口,那么隐式实现就不存在了。这就是接口只能被实现一次:

 

public interface IA
{
  void Test();
}

public interface IB
{
  void Test();
}


public class TestClass : IA, IB
{

  public void Test()
  {
    throw new NotImplementedException();
  }

  void IB.Test()
  {
    throw new NotImplementedException();
  }
}

 

在这段代码中,第一个Test方法实际上只实现了IA.Test,而如果将第二个Test方法去掉,则第一个方法就会同时实现两个Test,即IA.Test和IB.Test。不妨自己动手去试一下。

 

5、同一个类型也只能实现同一个接口一次。

其实不论类型的父类重复实现了同一个接口多少次,一个类型只能实现一个接口一次。考虑下面的代码:

public interface ITest
{
  void Test();
}

public abstract class Test1 : ITest
{
  public void Test()
  {
    throw new NotImplementedException();
  }
}

public abstract class Test2 : Test1, ITest
{
  public new void Test()
  {
    throw new NotImplementedException();
  }
}

public abstract class Test3 : Test2, ITest
{
  public new void Test()
  {
    throw new NotImplementedException();
  }
}

无论在基类中实现了多少次ITest接口,事实上在ITest3的接口列表中永远只有一个ITest(GetInterfaces),也只能对ITest的每个成员实现一次(隐式或显式)。

 

6、实现接口的成员实际可见性总是不低于接口可见性。显示实现接口的成员可见性总是等同于接口可见性(因为它们默认都是private的)。

重写基类成员的成员必须有相同的可见性,接口则不必,隐式实现总是要求成员必须为public。同时,一个类型可以实现可见性比自己高或低的接口,但不能继承可见性比自己低的基类。

因为能访问到接口的类型的地方一定能访问到接口的实现成员,所以实现接口的成员的实际可见性总不低于接口。

一个很常见的手法,一个可见性较低的类型通过可见性高的接口来暴露自己的成员,事实几乎所有的枚举器(IEnumerator的实例)都是private的类型,透过接口来暴露功能。

 

当然,你也可以设计一个internal的接口,然后让某个类型去“显示实现”,这个时候实现接口的成员的“实际”可见性将跟随接口变为internal。

由于隐式实现接口要求成员必须是public的,如果要用可见性较低的接口来制约成员可见性就只能用显式实现接口成员。也许有人会很奇怪我干嘛降级成员的可见性?因为有时候可见性高了并不是什么好事,它会破坏组件(不是类型)的封装。当然更多的时候,我们只是需要一个手段来统一设置某些成员的可见性。

 

不过,我们也能想出更多非常好玩的玩法:

public abstract class Test
{
  protected interface ITest
  {
    void Test();
  }

  public class Nested : ITest
  {

    void ITest.Test()
    {
      throw new NotImplementedException();
    }
  }
}

这也是显示实现接口特性除了解决名称和冲突问题之外最大的用处。

 
 
 
 
21
0
 
(请您对文章做出评价)
 
« 上一篇:大家都有头像,我来测试下我的新头像。
» 下一篇:当dynamic遭遇匿名对象所造成的问题初步研究结论。

 
Add your comment

 

 
  1. #1楼 xiao_p   2010-05-17 09:44
    显示实现接口

    就这个东西我在面试的时候问过n次,没有一个人知道。。。

    恩,我的意思是说,大家对于这个特性的使用应该是很少的,以至于忽略不计。
     
  2. #2楼 xiaGG   2010-05-17 10:14
    显示实现接口(目地),为了避免实现的不正确性,和解决有相同签名的不同成员
    显示实现接口(使用),要使用接口名加成员名来限定,并且要通过接口去访问成员,不能通过具体类去使用
     
  3. #3楼 技术,趋势   2010-05-17 10:39
    一楼说的好,显示接口,的确除框架,比如IEnumerator这类会应用外,实际工作中几乎用不上,实际上用了反而更糟,会造成代码的难于理解.
    致于多接口继承,其实也是一样的会增加复杂性而难于理解.
    在应用中,最佳方法就是单接口的应用.每一个接口,都只定义一件明确的事情.
     
  4. #4楼 装配脑袋   2010-05-17 10:46
    显式实现是C#的语法糖,CLR是用Attribute来标记运行时方法表内的override关系的,所以可以用标记任意控制哪个方法覆盖基类或接口中的哪个方法。VB和C++/CLI中就直接给用户暴露了这种能力。
     
  5. #5楼 钧梓昊逑   2010-05-17 12:37
    引用技术,趋势:
    一楼说的好,显示接口,的确除框架,比如IEnumerator这类会应用外,实际工作中几乎用不上,实际上用了反而更糟,会造成代码的难于理解.
    致于多接口继承,其实也是一样的会增加复杂性而难于理解.
    在应用中,最佳方法就是单接口的应用.每一个接口,都只定义一件明确的事情.

    你说的也对,不过掌握像这种语法特性目的不是为了使用而使用,只是我们多了一种工具,在某种特定的情况下,可以使用这些工具来解决遇到的问题。
     
  6. #6楼 钧梓昊逑   2010-05-17 12:53
    ……
     
  7. #7楼[楼主] Ivony...   2010-05-17 15:54
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public IPublicService
    {
      //需要暴露的功能和服务
    }
      
    public class SomeType
    {
      public IPublicService Service//通过接口暴露服务
      {
        get{ return new ServiceImplement ( this ); }
      }
      
      private class ServiceImplement : IPublicService//尽管嵌在宿主类内,但内嵌类型自身的封装性是有充分保证的,宿主类是不能访问内嵌类的私有或受保护成员的。
      {
        //内嵌类可以不受任何限制的访问宿主类的一切资源,很多时候这很方便。而且这种特权仅对内嵌类有效,所以不会影响宿主类的封装。
      }
    }


    这是一种非常常见的手法,也非常有用,利用内嵌类在类型内部划出更细一层的封装,又可以通过接口暴露内嵌类的功能。

    参考应用场景:
    比如说我们需要类型提供一个Provider,这个Provider逻辑相对独立,与类型自身的职责没啥关系,但又必须侵入类型才能得到自己所需要的东西。

    所以枚举器相当典型。



    至于实际工作中用不上,那主要取决于实际工作是什么了。
     
  8. #8楼 pk的眼泪   2010-06-05 00:29
    不错,学习了。
     
  9. #9楼 龙心   2010-06-07 09:21
    我是菜鸟,有点蒙
     
  10. #10楼 beikx   2010-06-13 00:36
    引用钧梓昊逑:
    引用技术,趋势:
    一楼说的好,显示接口,的确除框架,比如IEnumerator这类会应用外,实际工作中几乎用不上,实际上用了反而更糟,会造成代码的难于理解.
    致于多接口继承,其实也是一样的会增加复杂性而难于理解.
    在应用中,最佳方法就是单接口的应用.每一个接口,都只定义一件明确的事情.

    你说的也对,不过掌握像这种语法特性目的不是为了使用而使用,只是我们多了一种工具,在某种特定的情况下,可以使用这些工具来解决遇到的问题。


    向类实例隐藏接口函数的时候会用到,不过此类需求很少
posted on 2013-05-28 15:19  路漫漫,求索不息  阅读(749)  评论(0编辑  收藏  举报