代码改变世界

C# Interface 研究

2011-06-05 00:08  唐郎  阅读(1690)  评论(6编辑  收藏  举报

C# Interface 研究

1.   接口实现原理:

测试代码如下:

interface IA
{
    void Print();
}
class A1 : IA { public virtual void Print() { } }
class A2 : A1 { public override void Print() { } }

1.1 编译后源码分析

Interface 编译为IL后,有特有的interface修饰:

.class private interface abstract auto ansi IA

{

    // 接口方法默认为abstract virtual修饰,但实现接口的类中,默认为final修饰

    // 因此若要按继承传递,则需要在实现接口的基类中添加virtual修饰

    .method public hidebysig newslot abstract virtual instance void Print() cil managed

    {

    } // end of method IA::Print

} // end of class IA

 

 class A1 : IA { public virtual void Print() { } }

 Class A1编译为IL后, 默认继承于System.Object类:

 .class private auto ansi beforefieldinit A1

  extends [mscorlib]System.Object  // A1继承于Object, 而接口不会

  implements IA

{

    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed

    {

    } // default constructor

    .method public hidebysig newslot virtual instance void Print() cil managed

    {

    }

}

// 注意这里的接口实现:

// 不加virtual,默认为final修饰: 

.method public hidebysig newslot virtual final instance void  Print() cil managed { }

// 加virtual以后:

.method public hidebysig newslot virtual instance void  Print() cil managed { }

class A2 : A1 { public override void Print() { } }

 

Class A2编译为IL后, 仅继承于Class A1, 可见Interface本身不会随继承传递:

.class private auto ansi beforefieldinit A2

    extends A1

{

    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed

    {

    } // default constructor

    .method public hidebysig virtual instance void Print() cil managed

    {

    }

}

// 注意这里Print() 方法:

.method public hidebysig virtual instance void Print() cil managed

// 不加override默认为New修饰:

.method public hidebysig instance void Print() cil managed

1.2接口调用过程分析

尝试调用接口:

static void Main()
{
    IA ia = new A1();
    A1 a1 = new A2();

    // 因为有virtual修饰, 因此调用A2.Print(), 否则为A1.Print()
    ia.Print();
    a1.Print();

    ia.ToString();
    a1.ToString();
}

 // 注意这里ia.ToString()方法:

// 会给人一种暗示: IA引用可以调用ToString()方法, 必然来源于System.Object, 造成IA

// 承于System.Object的假象.

 

继续深入, 观察Main()函数编译后的IL源码:

.method private hidebysig static void Main() cil managed

{

    .entrypoint

    .maxstack 1

    .locals init (

        [0] class IA ia,

        [1] class A1 a1)

    L_0000: nop

    L_0001: newobj instance void A1::.ctor()

    L_0006: stloc.0

    L_0007: newobj instance void A1::.ctor()

    L_000c: stloc.1

    L_000d: ldloc.0

    L_000e: callvirt instance void IA::Print()

    L_0013: nop

    L_0014: ldloc.1

    L_0015: callvirt instance void A1::Print()

    L_001a: nop

    L_001b: ldloc.0

    L_001c: callvirt instance string [mscorlib]System.Object::ToString()

    L_0021: pop

    L_0022: ldloc.1

    L_0023: callvirt instance string [mscorlib]System.Object::ToString()

    L_0028: pop

    L_0029: ret

}

// 注意接口调用的IL, 并不直接编译为对接口引用对象的调用

// 这是一种独立的调用约定, 实现上完全不同于直接调用和虚表多态机制

//  callvirt instance void IA::Print()

 

// 对于 callvirt instance string [mscorlib]System.Object::ToString() 

// 因为所有可以实例化的对象默认继承自System.Object, 因此不必考虑对象类型

//  ia.ToString() a1.ToString() 调用过程相同

1.3 结论

 1.   接口不是Class, 地位与Class平级,虽与Class共享部分实现方式(IL“.class”),但功能和用法都与Class不同,不继承任何基类。

2.   接口和abstract class不同。abstract class则可以拥有成员,依然继承于System.Object类。

3.   只有接口引用而没有接口实例。C#中一切对象 默认 从System.Object继承。因此,可以简单的认为:System.Object是C#所有对象的“基类”,“万物之源”。

1.4 补充说明

C#中一切对象都必然从System.Object继承吗?其实不然。在命令行下用ILASM.exe编译IL时,有一个可选参数:/NOAUTOINHERIT,解释如下:

/NOAUTOINHERIT  Disable inheriting from System.Object by default

2.   使用接口的两种方法

1.    直接在基类中加入virtual修饰,则无论通过基类引用还是接口引用调用都可以实现多态。子类无需继承同一个接口。

2.    所有类型都自己实现接口

示例:

static void InterfaceTest()
{
    // A2不直接实现接口IA,则调用遵循多态规则,无virtual则调用基类
   IA ia = new A2();
    ia.Print();

    A1 a1 = new A2();
    a1.Print();
}

 

3.   IL指令参考:

   hidebysig:类可以从其它多个类中派生。hidebysig特性保证了父类中的函数在具有相同名称或签名的派生类中会被隐藏。在这个例子中,它保证了如果函数vijay出现在基类中,那么它在派生类中就是不可见的。

   static:带有entrypoint指令的函数必须是静态 的。静态函数必须具有相关联的实体或者源代码,并且使用类型名称而不是实例名称来引用它们。

   il managed:由于它的复杂性质,我们将关于这个特性的解释延后。当时机成熟时,它的功能将会被解释清楚。

   auto:这表示类在内存中的布局将只由运行时来决定,而不是由我们的程序决定。

   ansi:源代码通常被划分为两个主要的类别:托管代码和非托管代码。

      以诸如C语言编写的代码被称为非托管代码或不可信任的代码。我们需要一个特性来处理非托管代码和托管代码之间的互操作。例如,当我们想要在托管和非托管代码之间转移字符串时,这个特性会被使用到。

      如果我们跨越托管代码的边界并进入非托管代码的领域,那么一个字符串——由2字节Unicode字符组成的数组,将会被转换为一个ANSI字符串——由1字节ANSI字符组成的数组;反之亦然。修饰符ansi用于消除托管和非托管代码之间的转换。

      更多IL应用,参考《C# to IL》。