• (翻译) 《C# to IL》第四章 关键字和操作符


    -4-

     

    关键字和操作符

     

    位于return语句之后的代码是不会被执行的。在下面给出的第1个程序中,你将发现在C#中有一个WriteLine函数调用,但是在我们的IL代码中却看不到。这是因为编译器意识到任何return之后的语句都不会被执行,从而,也就不用将其转换到IL中了。

    a.cs

    class zzz

    {

    public static void Main()

    {

    return;

    System.Console.WriteLine("hi");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    br.s IL_0002

    IL_0002: ret

    }

    }

     

          编译器不会在编译从不执行的代码上浪费时间,而是在遇到这种情形时生成一个警告。

     

    a.cs

    class zzz

    {

    public static void Main()

    {

    }

    zzz( int i)

    {

    System.Console.WriteLine("hi");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    .method private hidebysig specialname rtspecialname instance void .ctor(int32 i) il managed

    {

    ldarg.0

    call instance void [mscorlib]System.Object::.ctor()

    ldstr "hi"

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    如果在源代码中不存在构造函数,那么就会生成一个默认的无参构造函数。如果存在构造函数,那么这个无参构造函数就会从代码中被排除。

    基类的无参构造函数总是会被调用,并且会被首先调用。上面的IL代码证明了这一事实。

    a.cs

    namespace vijay

    {

    namespace mukhi

    {

    class zzz

    {

     public static void Main()

    {

    }

    }

    }

    }

     

    a.il

    .assembly mukhi {}

    .namespace vijay.mukhi

    {

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

    }

     

    我们可能会在一个命名空间中编写另一个命名空间,但是编译器会将它们全都转换为IL文件的一个命名空间中。从而,C#文件中的这两个命名空间vijaymukhi都会被合并到IL文件的一个单独的命名空间vijay.mukhi中。

    a.il

    .assembly mukhi {}

    .namespace vijay

    {

    .namespace mukhi

    {

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

    }

    }

     

    C#中,一个命名空间可以出现在另一个命名空间中,但是C#编译器更喜欢只使用一个单独的命名空间,从而IL输出只显示了一个命名空间。IL中的.namespace指令在概念上类似于C#中的namespace关键字。命名空间的观点起源于IL而不是C#这样的程序语言。

    a.cs

    namespace mukhi

    {

    class zzz

    {

    public static void Main()

    {

    }

    }

    }

    namespace mukhi

    {

    class pqr

    {

    }

    }

     

    a.il

    .assembly mukhi {}

    .namespace mukhi

    {

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

    .class private auto ansi pqr extends [mscorlib]System.Object

    {

    }

    }

     

    C#文件中,我们可能有2个名为mukhi命名空间,但是它们会变成IL文件中的一个大的命名空间,而它们的内容会被合并。合并命名空间的工具是由C#编译器提供的。

    设计者认为这么处理是恰当的——他们本可以将上面的程序替代地标记为一个错误。

    a.cs

    class zzz

    {

    public static void Main()

    {

    int i = 6;

    zzz a = new zzz();

    a.abc(ref i);

    System.Console.WriteLine(i);

    }

    public void abc(ref int i)

    {

    i = 10;

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (int32 V_0,class zzz V_1)

    ldc.i4.6

    stloc.0

    newobj instance void zzz::.ctor()

    stloc.1

    ldloc.1

    ldloca.s V_0

    call instance void zzz::abc(int32&)

    ldloc.0

    call void [mscorlib]System.Console::WriteLine(int32)

    ret

    }

    .method public hidebysig instance void abc(int32& i) il managed

    {

    ldarg.1

    ldc.i4.s   10

    stind.i4

    ret

    }

    }

     

    Output

    10

     

    我们现在要解释IL是如何实现传递引用的。与C#不同,在IL中可以很方便的使用指针。IL3种类型的指针。

    当函数abc被调用时,变量i会被作为一个引用参数传递到函数中。在IL中,ldloca.s指令会被调用,它把变量的地址放到栈上。替代地,如果这个指令是ldloc,那么就会把变量的值放到栈上。

           在函数调用中,我们添加符号&到类型名称的结尾来表示变量的地址。数据类型后面的&后缀表示变量的内存位置,而不是在变量中包括的值。

           在函数本身中,ldarg.1用于把参数1的地址放到栈上。然后,我们把想要初始化的数值放到栈上。在上面的例子中,我们首先把变量i的地址放到栈上,随后是我们想要初始化的值,即10。

           stind指令把出现在栈顶的值,也就是10,放到变量中,这个变量的地址存储为栈上的第2项。在这个例子中,因为我们传递变量i的地址到栈上,所以变量i分配到值10。

        当在栈上给出一个地址时,使用stind指令。它会使用特定的值填充该内存位置。

     

           如果使用关键字ref取代out,那么IL还是会显示相同的输出,因为不管是哪种情形,变量的地址都会被放到栈上。因此,refoutC#实现中的“人为”概念,而在IL中没有任何等价的表示。

    IL代码无法知道原始的程序使用的是ref还是out。因此,在反汇编这个程序时,我们将无法区别refout,因为这些信息在从C#代码到IL代码的转换中会丢失。

    a.cs

    class zzz

    {

    public static void Main()

    {

    string s = "hi" + "bye";

    System.Console.WriteLine(s);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class System.String V_0)

    ldstr      "hibye"

    stloc.0

    ldloc.0

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    Output

    hibye

     

    下面关注的是2个字符串的连接。C#编译器通过将它们转换为一个字符串来实现。这取决于编译器优化常量的风格。存储在局部变量中的值随后被放置在栈上,从而在运行期,C#编译器会尽可能的优化代码。

    a.cs

    class zzz

    {

    public static void Main()

    {

    string s = "hi" ;

    string t = s + "bye";

    System.Console.WriteLine(t);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class System.String V_0,class System.String V_1)

    ldstr      "hi"

    stloc.0

    ldloc.0

    ldstr "bye"

    call class System.String [mscorlib]System.String::Concat(class System.String,class System.String)

    stloc.1

    ldloc.1

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    Output

    hibye

     

    无论编译器何时对变量进行处理,都会在编译器间忽略它们的值。在上面的程序中会执行以下步骤:

    变量st会被相应地转换为V_0V_1

    为局部变量V_0分配字符串"hi"

    随后这个变量会被放到栈上。

    接下来,常量字符串"bye"会被放到栈上。

    之后,+操作符被转化为静态函数Concat,它属于String类。

    这个方法会连接两个字符串并在栈上创建一个新的字符串。

    这个合成的字符串会被存储在变量V_1中。

    最后,这个合成的字符串会被打印出来。

     

    C#中,有两个PLUS+)操作符。

    一个处理字符串。这个操作符会被转换为ILString类的Concat函数。

    另一个则处理数字。这个操作符会被转换为IL中的add指令。

     

    从而,String类和它的函数是在C#编译器中创建的。因此我们能够断定,C#可以理解并处理字符串运算。

    a.cs

    class zzz

    {

    public static void Main()

    {

    string a = "bye";

    string b = "bye";

    System.Console.WriteLine(a == b);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class System.String V_0,class System.String V_1)

    ldstr      "bye"

    stloc.0

    ldstr      "bye"

    stloc.1

    ldloc.0

    ldloc.1

    call bool [mscorlib]System.String::Equals(class System.String,class System.String)

    call void [mscorlib]System.Console::WriteLine(bool)

    ret

    }

    }

     

    Output

    True

     

    就像+操作符那样,当==操作符和字符串一起使用时,编译器会将其转换为函数Equals

           从上面的例子中,我们推论出C#编译器对字符串的处理是非常轻松的。下一个版本将会引进更多这样的类,编译器将会从直观上理解它们。

    a.cs

    class zzz

    {

    public static void Main()

    {

    System.Console.WriteLine((char)65);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldc.i4.s   65

    call void [mscorlib]System.Console::WriteLine(wchar)

    ret

    }

    }

     

    Output

    A

     

    无论我们何时转换一个变量,例如把一个数字值转换为一个字符值,在内部,程序仅调用了带有转换数据类型的函数。转换不能修改原始的变量。实际发生的是,在WriteLine被调用时带有一个wchar,而不是一个int。从而,转换不会导致任何运行期间的负载。

    a.cs

    class zzz

    {

    public static void Main()

    {

    char i = 'a';

    System.Console.WriteLine((char)i);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (wchar V_0)

    ldc.i4.s   97

    stloc.0

    ldloc.0

    call void [mscorlib]System.Console::WriteLine(wchar)

    ret

    }

    }

     

    Output

    a

     

    C#的字符数据类型是16字节大小。在转换为IL时,它会被转换为wchar。字符a会被转换为ASCII数字97。这个字符会被放在栈上并且变量V_0会被初始化为这个值。之后,程序会在屏幕上显示值a。

    a.cs

    class zzz

    {

    public static void Main()

    {

    System.Console.WriteLine('"u0041');

    System.Console.WriteLine(0x41);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldc.i4.s   65

    call void [mscorlib]System.Console::WriteLine(wchar)

    ldc.i4.s   65

    call void [mscorlib]System.Console::WriteLine(int32)

    ret

    ret

    }

    }

     

    Output

    A

    65

     

             IL不能理解字符UNICODE数字HEXADECIMAL。它更喜欢简单明了的十进制数字。转义符\u的出现为C#程序员带来了方便,极大提高的效率。

             你可能已经注意到,即使上面的程序有2套指令,但还是不会有任何错误生成。标准是——至少应该存在一个ret指令。

    a.cs

    class zzz

    {

    public static void Main()

    {

    int @int;

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (int32 V_0)

    ret

    }

    }

     

    C#中,在栈上创建的变量被转换为IL后不再具有原先给定的名称。因此,“C#保留字可能会在IL中产生问题”——这种情况是不会发生的。

    a.cs

    class zzz

    {

    int @int;

    public static void Main()

    {

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .field private int32 'int'

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

     

    在上面的程序中,局部变量@int变成了一个名为int的字段。而数据类型int改变为int32,后者是IL中的保留字。之后,编译器在一个单引号内写字段名称。在转换到IL的过程中,@符号会直接从变量的名称中消失。

    a.cs

    // hi this is comment

    class zzz

    {

    public static void Main() // allowed here

    {

    /*

    A comment over

    two lines

    */

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

     

    当你看到上面的代码时,你将理解为什么全世界的程序员都讨厌写注释。C#中的所有注释在生成的IL中都会被删除。单引号不会被复制到IL代码中。

           编译器对注释是缺乏“尊重”的,它会把所有的注释都扔掉。程序员认为写注释是徒劳的,他们会产生极大的挫折感——这并不奇怪。

    a.cs

    class zzz

    {

    public static void Main()

    {

    System.Console.WriteLine("hi "nBye"tNo");

    System.Console.WriteLine("""");

    System.Console.WriteLine(@"hi "nBye"tNo");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldstr      "hi "nBye"tNo"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ldstr      """"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ldstr      "hi ""nBye""tNo"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    Output

    hi

    Bye No

    "

    hi "nBye"tNo

     

    C#处理字符串的能力是从IL中继承而来的。像\n这样的转义符会被直接复制。

           双斜线\\,在显示时,结果是一个单斜线\

           如果一个字符串以一个@符号作为开始,在该字符串中的特殊意思就是这个转移符会被忽略,而这个字符串会被逐字显示,正如上面的程序所显示的那样。

           如果IL没有对字符串格式提供支持,那么它就会烦心于要处理大多数现代程序语言的所面临的困境。

    a.cs

    #define vijay

    class zzz

    {

    public static void Main()

    {

    #if vijay

    System.Console.WriteLine("1");

    #else

    System.Console.WriteLine("2");

    #endif

    }

    }

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed {

    .entrypoint

    ldstr      "1"

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    ret

    }

    }

     

    Output

    1

     

    接下来的一系列程序与预处理指令有关,这与C#编译器是不同的。只有预处理指令能够理解它们。

           在上面的.cs程序中,#define指令创建了一个名为"vijay"的词。编译器知道#if语句是TRUE,因此,它会忽略#else语句。从而,所生成的IL文件只包括具有参数'1'WriteLine函数,而不是具有参数'2'的那个。

    这就涉及到了编译期间的知识。大量不会使用到的代码,会在被转换为IL之前,被预处理直接除去。

    a.cs

    #define vijay

    #undef vijay

    #undef vijay

    class zzz

    {

    public static void Main()

    {

    #if vijay

    System.Console.WriteLine("1");

    #endif

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

     

    我们可以使用很多#undef语句,只要我们喜欢。编译器知道'vijay'这个词被事先定义了,之后,它会忽略#if语句中的代码。

           在从ILC#的再次转换中,原始的预处理指令是无法被恢复的。

    a.cs

    #warning We have a code red

    class zzz

    {

    public static void Main()

    {

    }

    }

     

               

           C#中的预处理指令#warning,用于为运行编译器的程序员显示警告。

           预处理指令#line#error并不会生成任何可执行的输出。它们只是用来提供信息。

     

    继承 

    a.cs

    class zzz

    {

    public static void Main()

    {

    xxx a = new xxx();

    a.abc();

    }

    }

    class yyy

    {

    public void abc()

    {

    System.Console.WriteLine("yyy abc");

    }

    }

    class xxx : yyy

    {

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class xxx V_0)

    newobj instance void xxx::.ctor()

    stloc.0

    ldloc.0

    call instance void yyy::abc()

    ret

    }

    }

    .class private auto ansi yyy extends [mscorlib]System.Object

    {

    .method public hidebysig instance void abc() il managed

    {

    ldstr      "yyy abc"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

    .class private auto ansi xxx extends yyy

    {

    }

     

    Output

    yyy abc

     

    继承的概念在所有支持继承的程序语言中都是相同的。单词extends起源于IL和Java而不是C#。

    当我们编写a.abc()时,编译器决定在abc函数中的调用要基于下面的标准:

    如果类xxx有一个函数abc,那么在函数vijay中的调用将具有前缀xxx。

    如果类yyy有一个函数abc,那么在函数vijay中的调用将具有前缀yyy。

     

    之后,人工智能决定了关于哪个函数abc会被调用,它驻留于编译器中而不是生成的IL代码中。

    a.cs

    class zzz

    {

    public static void Main()

    {

    yyy a = new xxx();

    a.abc();

    }

    }

    class yyy

    {

    public virtual void abc()

    {

    System.Console.WriteLine("yyy abc");

    }

    }

    class xxx : yyy

    {

    public new void abc()

    {

    System.Console.WriteLine("xxx abc");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class yyy V_0)

    newobj instance void xxx::.ctor()

    stloc.0

    ldloc.0

    callvirt instance void yyy::abc()

    ret

    }

    }

    .class private auto ansi yyy extends [mscorlib]System.Object

    {

    .method public hidebysig newslot virtual instance void abc() il managed

    {

    ldstr      "yyy abc"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

    .class private auto ansi xxx extends yyy

    {

    .method public hidebysig instance void abc() il managed

    {

    ldstr      "xxx abc"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

     

    Output

    yyy abc

     

           在上面程序的上下文中,我们要向C#新手多做一点解释。

           我们能够使基类的一个对象和派生类xxx的一个对象相等。我们调用了方法a.abc()。随之出现的问题是,函数abc的下列2个版本,哪个将会被调用?

    出现在基类yyy中的函数abc,调用对象属于这个函数。

    函数abc存在于类xxx中,它会被初始化为这个类型。

     

    换句话说,是编译期间类型有意义,还是运行期间的类型有意义?

           基类函数具有一个名为virtual的修饰符,暗示了派生类能覆写这个函数。派生类,通过添加修饰符new,通知编译器——这个函数abc与派生类的函数abc无关。它会把它们当作单独的实体。

    首先,使用ldloc.0把this指针放到栈上,而不是使用call指令。这里有一个callvirt作为替代。这是因为函数abc是虚的。除此之外,没有区别。类yyy中的函数abc被声明为虚的,还被标记为newslot。这表示它是一个新的虚函数。关键字new位于C#的派生类中。

           IL还使用了类似于C#的机制,来断定哪个版本的abc函数会被调用。

    a.cs

    class zzz

    {

    public static void Main()

    {

    yyy a = new xxx();

    a.abc();

    }

    }

    class yyy

    {

    public virtual void abc()

    {

    System.Console.WriteLine("yyy abc");

    }

    }

    class xxx : yyy

    {

    public override void abc()

    {

    System.Console.WriteLine("xxx abc");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class yyy V_0)

    newobj     instance void xxx::.ctor()

    stloc.0

    ldloc.0

    callvirt   instance void yyy::abc()

    ret

    }

    }

    .class private auto ansi yyy extends [mscorlib]System.Object

    {

    .method public hidebysig newslot virtual instance void abc() il managed

    {

    ldstr      "yyy abc"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

    .class private auto ansi xxx extends yyy

    {

    .method public hidebysig virtual instance void abc() il managed

    {

    ldstr      "xxx abc"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

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

    {

    ldarg.0

    call instance void yyy::.ctor()

    ret

    }

    }

     

    Output

    xxx abc

     

    如果类xxx的基构造函数没有被调用,那么在输出窗体中就不会有任何显示。通常,我们不会在IL程序中包括默认的无参构造函数。

           如果没有关键字newoverride,默认使用的关键字就是new。在上面的类xxx的函数abc中,我们使用到了override关键字,它暗示了这个函数abc覆写了基类的函数。

           IL默认调用对象所属类的虚函数,并使用编译期间的类型。在这个例子中,它是yyy

    随着在派生类中的覆写而发生的第1个改变是,除函数原型外还会多一个关键字virtual。之前并没有提供new,因为函数new是和隔离于基类中的函数一起被创建的。

    override的使用有效地实现了对基类函数的覆写。这使得函数abc成为类xxx中的一个虚函数。换句话说,override变成了virtual,而new则会消失。

           因为在基类中有一个newslot修饰符,并且在派生类中有一个具有相同名称的虚函数,所以派生类会被调用。

           在虚函数中,对象的运行期间类型会被优先选择。指令callvirt在运行期间解决了这个问题,而不是在编译期间。

    a.cs

    class zzz

    {

    public static void Main()

    {

    yyy a = new xxx();

    a.abc();

    }

    }

    class yyy

    {

    public virtual void abc()

    {

    System.Console.WriteLine("yyy abc");

    }

    }

    class xxx : yyy

    {

    public override void abc()

    {

    base.abc();

    System.Console.WriteLine("xxx abc");

    }

    }

     

     

    a.il

    .method public hidebysig virtual instance void abc() il managed

    {

    ldarg.0

    call       instance void yyy::abc()

    ldstr      "xxx abc"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

     

    在类xxx中只有函数abc会在上面显示。剩下的IL代码会被省略。base.abc()调用基类的函数abc,即类yyy。关键字base是内存中指向对象的一个引用。C#的这个关键字不能被IL所理解,因为它是一个编译期间的问题。base不关心函数是不是虚的。

           无论我们何时首次创建一个虚方法,将它标注为newslot是一个好主意,只是为了表示存在于超类中具有相同名称的所有函数中的一个断点。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    newobj instance void yyy::.ctor()

    callvirt instance void iii::pqr()

    ret

    }

    }

    .class interface iii

    {

    .method public virtual abstract void pqr() il managed

    {

    }

    }

    .class public yyy implements iii

    {

    .override iii::pqr with instance void yyy::abc()

    .method public virtual hidebysig newslot instance void abc() il managed

    {

    ldstr "yyy abc"

    call void System.Console::WriteLine(class System.String)

    ret

    }

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

    {

    ldarg.0

    call instance void [mscorlib]System.Object::.ctor()

    ret

    }

    }

     

    Output

    yyy abc

     

    我们创建了一个接口iii,它只有一个名为pqr的函数。然后,类yyy实现了接口iii,但是没有实现函数pqr,而是添加了一个名为abc的函数。在入口点函数vijay中,函数pqr会被接口iii调用。

    我们之所以没有得到任何错误,是因为override指令的存在。这个指令通知编译器重定向对接口iii的函数pqr以及对类yyy的函数abc的任何调用。编译器对override指令是非常严格的。可以从这样的事实中对此进行考量——如果在类yyy的定义中没有实现iii,那么我们就会得到下列异常:

    Output

    Exception occurred: System.TypeLoadException: Class yyy tried to override method pqr but does not implement or inherit that methods.

       at zzz.vijay()

     

    析构函数

    a.cs

    class zzz

    {

    public static void Main()

    {

    }

    ~zzz()

    {

    System.Console.WriteLine("hi");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    .method family hidebysig virtual instance void Finalize() il managed

    {

    ldstr      "hi"

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ldarg.0

    call       instance void [mscorlib]System.Object::Finalize()

    ret

    }

    }

     

    No output

     

    析构函数被转换为Finalize函数。在C#文档中也制定了这条信息。Finalize函数的调用源于Object。文本"hi"不会显示,因为只要运行时决定了,这个函数就会被调用。我们所知道的全部是——在对象“死亡”时Finalize就会被调用。因此,无论何时一个对象“死亡”,它都会调用Finalize。没有办法销毁任何事物,包括.NET对象在内。

    a.cs

    class zzz

    {

    public zzz()

    {

    }

    public zzz(int i)

    {

    }

    public static void Main()

    {

    }

    ~zzz()

    {

    System.Console.WriteLine("hi");

    }

    }

    class yyy : zzz

    {

    }

     

    a.il

    .class private auto ansi yyy extends zzz

    {

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

    {

    ldarg.0

    call instance void zzz::.ctor()

    ret

    }

    }

     

    在上面的代码中,我们只显示了类yyy。即使我们有2个构造函数和1个析构函数,类yyy只接收默认的无参构造函数。因此,派生类不会从基类中继承构造函数和析构函数。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    call void yyy::abc()

    ret

    }

    }

    .class private auto ansi yyy extends [mscorlib]System.Array

    {

    .method public hidebysig static void abc() il managed

    {

    ldstr "hi"

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    Output

    hi

     

    在C#中,不允许我们从像System.Array这样的类中派生一个类,在IL中没有这样的约束。因此,上面的代码不会生成任何错误。

           我们确实能够推断出C#编译器具有上面的约束而IL的约束则比较少。一门语言的规则是由编译器在编译期间决定的。

           需要说明的是,在C#中,有一些类,是我们不能从中派生的——DelegateEnumValueType

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class aa V_0)

    newobj instance void aa::.ctor()

    stloc.0

    ret

    }

    }

    .class public auto ansi aa extends bb

    {

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

    {

    ldarg.0

    call instance void bb::.ctor()

    ldstr "aa"

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

    .class public auto ansi bb extends cc

    {

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

    {

    ldarg.0

    call       instance void cc::.ctor()

    ldstr      "bb"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

    .class public auto ansi cc extends aa

    {

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

    {

    ldarg.0

    call       instance void aa::.ctor()

    ldstr      "cc"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    Error

    Exception occurred: System.TypeLoadException: Could not load class 'aa' because the format is bad (too long?)

       at zzz.vijay()

     

    C#中,循环引用是禁止的。编译器会检查循环引用,并且如果发现了它,就会报告一个错误。然而,IL并不检查循环引用,因为Microsoft不希望所有的程序员都使用纯的IL

           因此,类aa继承自类bb,类bb继承自类cc,最后类cc又继承自类aa。这就形成了一个循环引用。在运行时抛出的异常不会给出循环引用的任何迹象。从而,如果我在这里没有为你揭示这个秘密,那么这个异常就可能让你感到困惑。我并不打算显摆对理解IL有多深这样的事实,但是偶尔给出一些提示信息是无妨的。

    a.cs

    internal class zzz

    {

    public static void Main()

    {

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

     

    访问修饰符,如关键字internal,只是C#词法的一部分,而与IL没有任何关系。关键字internal表示这个特定的类只能在它所在的文件中被访问到。

           因此,通过掌握IL,我们能够区分.NET核心和C#领域存在的特性之间的不同。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

    .class public auto ansi yyy extends xxx

    {

    }

    .class private auto ansi xxx extends [mscorlib]System.Object

    {

    }

     

    C#中,有一条规则:基类的可访问性要大于派生类。这条规则在IL中不适用。从而,即使基类xxx是私有的而派生类yyy是公共的,也不会在IL中生成任何错误。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

     

    C#中,一个函数的可访问性不能大于它所在类的可访问性。函数vijay是公有的,然而它所在的类却是私有的。因此,这个类对包含在它内部的函数具有更多的约束。再说一遍,在IL中没有强加这样的约束。

    a.cs

    class zzz

    {

    public static void Main()

    {

    yyy a = new yyy();

    xxx b = new xxx();

    a = b;

    b = (xxx) a;

    }

    }

    class yyy

    {

    }

    class xxx : yyy

    {

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class yyy V_0,class xxx V_1)

    newobj     instance void yyy::.ctor()

    stloc.0

    newobj     instance void xxx::.ctor()

    stloc.1

    ldloc.1

    stloc.0

    ldloc.0

    castclass xxx

    stloc.1

    ret

    }

    }

    .class private auto ansi yyy extends [mscorlib]System.Object

    {

    }

    .class private auto ansi xxx extends yyy

    {

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

    {

    ldarg.0

    call       instance void yyy::.ctor()

    ret

    }

    }

     

           如果在xxx中没有构造函数,那么就会抛出下列异常:

    Output

    Exception occurred: System.InvalidCastException: An exception of type System.InvalidCastException was thrown.

       at zzz.vijay()

     

           在上面的例子中,我们创建了2个对象ab,它们分别是类yyyxxx的实例。类xxx是派生类而yyy是基类。我们能写出a=b,如果我们使一个派生类和一个基类相等,那么就会生成一个错误。因此,就需要一个转换操作符。

           C#中,cast会被转换为castclass指令,后面紧跟着派生类的名称,也就是要被转换到的类。如果它不能被转换,就会触发上面提到的异常。

           在上面的代码中,没有构造函数,从而,就会生成异常。 

           因此,IL具有大量高级的用来处理对象和类的准则。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class yyy V_0,class xxx V_1)

    newobj     instance void yyy::.ctor()

    stloc.0

    newobj     instance void xxx::.ctor()

    stloc.1

    ldloc.1

    stloc.0

    ldloc.0

    castclass xxx

    stloc.1

    ret

    }

    }

    .class private auto ansi yyy extends [mscorlib]System.Object

    {

    }

    .class private auto ansi xxx extends [mscorlib]System.Object

    {

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

    {

    ldarg.0

    call instance void System.Object::.ctor()

    ret

    }

    }

     

    在上面的例子中,类xxx不再从类yyy中派生。它们都是从Object类中派生的。但是,我们可以把类yyy转换为类xxx。在带有构造函数的类xxx中不会生成任何错误,但是如果移除了这个构造函数,就会生成异常。IL还具有它自己的独特工作方式。

    a.il

    .assembly mukhi {}

    .class private auto ansi sealed zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ret

    }

    }

    .class private auto ansi yyy extends zzz

    {

    }

     

    文档非常清晰地表示了一个密闭类不能被进一步扩展或子类化。在这个例子中,我们希望看到一个错误但是什么也不会生成。必须提醒你的是,我们现在使用的是beta版本。下一个版本可能会生成一个错误。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class yyy V_0)

    newobj     instance void yyy::.ctor()

    stloc.0

    ret

    }

    }

    .class private auto ansi abstract yyy

    {

    }

     

    抽象类不能被直接使用。只能从中派生。上面的代码应该生成一个错误,但并不是这样。

    a.cs

    public class zzz

    {

    const int i = 10;

    public static void Main()

    {

    System.Console.WriteLine(i);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldc.i4.s   10

    call void [mscorlib]System.Console::WriteLine(int32)

    ret

    ret

    }

    }

     

    Output

    10

     

    常量是只存在于编译期间的一个实体。它在运行期间是不可见的。这就证实了编译器会移除对编译期间对象的所有跟踪。在转换到IL的过程中,在C#中出现的所有int i都会被数字10取代。

    a.cs

    public class zzz

    {

    const int i = j + 4;

    const int j = k - 1;

    const int k = 3;

    public static void Main()

    {

    System.Console.WriteLine(k);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .field private static literal int32 i = int32(0x00000006)

    .field private static literal int32 j = int32(0x00000002)

    .field private static literal int32 k = int32(0x00000003)

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldc.i4.3

    call void [mscorlib]System.Console::WriteLine(int32)

    ret

    }

    }

    Ouput

    3

     

    所有的常量都是由编译器计算的,即使它们可能关联到其它常量,但它们会被设定为一个绝对的值。IL运行时不会为文本字段分配任何内存。这涉及到元数据的领域,稍后我们将对其分析。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .field private static literal int32 i = int32(0x00000006)

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldc.i4.6

    stsfld int32 zzz::i

    ret

    }

    }

     

    Output

    Exception occurred: System.MissingFieldException: zzz.i

       at zzz.vijay()

     

    文本字段表示一个常量值。在IL中,不允许访问任何文本字段。编译器在编译期间不会生成任何错误,但是在运行期间会抛出一个异常。我们希望一个编译期间错误,因为我们在指令stsfld中使用了一个文本字段。

    a.cs

    public class zzz

    {

    public static readonly int i = 10;

    public static void Main()

    {

    System.Console.WriteLine(i);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .field public static initonly int32 i

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldsfld int32 zzz::i

    call void [mscorlib]System.Console::WriteLine(int32)

    ret

    }

    .method public hidebysig specialname rtspecialname static void .cctor() il managed

    {

    ldc.i4.s   10

    stsfld int32 zzz::i

    ret

    }

    }

     

    Output

    10

     

    只读字段不能被修改。在IL中,有一个名为initonly的修饰符,它实现了相同的概念。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .field public static initonly int32 i

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldc.i4.s   10

    stsfld int32 zzz::i

    ldsfld int32 zzz::i

    call void [mscorlib]System.Console::WriteLine(int32)

    ret

    }

    }

     

    文档非常清晰地表明了只读字段只能在构造函数中改变,但是CLR不会严格地对此进行检查。可能在下一个版本,他们应该注意这样的事情。

           因此,在readonly上的全部约束,必须由(将源代码转换为IL的)程序语言强制执行。我们没有试图在IL上运行,但是IL希望有人在这种情形中进行错误检查。

    a.cs

    public class zzz

    {

    public static void Main()

    {

    zzz a = new zzz();

    pqr();

    a.abc();

    }

    public static void pqr()

    {

    }

    public void abc()

    {

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .field public static initonly int32 i

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class zzz V_0)

    newobj instance void zzz::.ctor()

    stloc.0

    call void zzz::pqr()

    ldloc.0

    call instance void zzz::abc()

    ret

    }

    .method public hidebysig static void pqr() il managed

    {

    ret

    }

    .method public hidebysig instance void abc() il managed

    {

    ret

    }

    }

     

    这个例子是一个更新过的版本。静态函数pqr不会传递这个指针到栈上,但是,非静态函数abc会把这个指针或引用传递到它的变量存储在内存中的位置。

    因此,在调用函数abc之前,指令ldloc.0会把zzz的引用放到栈上。

    a.cs

    public class zzz

    {

    public static void Main()

    {

    pqr(10,20);

    }

    public static void pqr(int i , int j)

    {

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .field public static initonly int32 i

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    ldc.i4.s   10

    ldc.i4.s   20

    call void zzz::pqr(int32,int32)

    ret

    }

    .method public hidebysig static void pqr(int32 i,int32 j) il managed

    {

    ret

    }

    }

     

    调用约定指出了这些参数应该被放到栈上的顺序。在IL中默认的顺序是它们被写入的顺序。因此,数字10会首先进栈,之后是数字20

           Microsoft实现了相反的顺序。从而,20会首先进栈,之后是10。我们不能推论出这个特性。

    a.cs

    public class zzz

    {

    public static void Main()

    {

    bb a = new bb();

    }

    }

    public class aa

    {

    public aa()

    {

    System.Console.WriteLine("in const aa");

    }

    public aa(int i)

    {

    System.Console.WriteLine("in const aa" + i);

    }

    }

    public class bb : aa

    {

    public bb() : this(20)

    {

    System.Console.WriteLine("in const bb");

    }

    public bb(int i) : base(i)

    {

    System.Console.WriteLine("in const bb" + i);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class bb V_0)

    newobj instance void bb::.ctor()

    stloc.0

    ret

    }

    }

    .class public auto ansi aa extends [mscorlib]System.Object

    {

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

    {

    ldarg.0

    call       instance void [mscorlib]System.Object::.ctor()

    ldstr      "in const aa"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    .method public hidebysig specialname rtspecialname instance void .ctor(int32 i) il managed

    {

    ldarg.0

    call       instance void [mscorlib]System.Object::.ctor()

    ldstr      "in const aa"

    ldarga.s   i

    box        [mscorlib]System.Int32

    call       class System.String [mscorlib]System.String::Concat(class System.Object,class System.Object)

    call void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

    .class public auto ansi bb extends aa

    {

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

    {

    ldarg.0

    ldc.i4.s   20

    call       instance void bb::.ctor(int32)

    ldstr      "in const bb"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    .method public hidebysig specialname rtspecialname instance void .ctor(int32 i) il managed

    {

    ldarg.0

    ldarg.1

    call       instance void aa::.ctor(int32)

    ldstr      "in const bb"

    ldarga.s   i

    box        [mscorlib]System.Int32

    call       class System.String [mscorlib]System.String::Concat(class System.Object,class System.Object)

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    Output

    in const aa20

    in const bb20

    in const bb

     

                我们只创建了一个对象,它是类bb的一个实例。有3个构造函数会被调用,而不是2个构造函数(一个是基类的,一个是派生类的)。

    IL中,首先,会调用没有参数的构造函数。

    然后,当到达构造函数bb时,就会对相同类的另一个带有参数值20的构造函数进行调用。This(20)会被转换为对一个实际的带有一个参数的构造函数的调用。

    现在,我们转移到bb的一个构造函数上。这里,初始化对aa的一个构造函数的调用,被作为需要首先被调用的基类的构造函数。

     

    幸运的是,aa的基类构造函数不会使我们徒劳无功。在它完成执行之后,就会显示这个字符串,而最后,bb的无参构造函数会被调用。

           因此,basethisIL中是不存在的,它们是编译期间被硬编译到IL代码中的“赝品”。

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class aa V_0)

    newobj instance void aa::.ctor()

    ret

    }

    }

    .class public auto ansi aa extends [mscorlib]System.Object

    {

    .method private hidebysig specialname rtspecialname instance void .ctor() il managed

    {

    ret

    }

    }

     

    Output

    Exception occurred: System.MethodAccessException: aa..ctor()

       at zzz.vijay()

     

                我们不能在类的外部访问它的私有成员。因此,正如我们在类bb中创建唯一的私有构造函数那样,我们不能创建任何看上去像类bb的对象。在C#中,同样的规则也适用于访问修饰符。

    a.cs

    public class zzz

    {

    public static void Main()

    {

    yyy a = new yyy();

    }

    }

    class yyy

    {

    public int i;

    public bool j;

    public yyy()

    {

    System.Console.WriteLine(i);

    System.Console.WriteLine(j);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class public auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (class yyy V_0)

    newobj     instance void yyy::.ctor()

    stloc.0

    ret

    }

    }

    .class private auto ansi yyy extends [mscorlib]System.Object

    {

    .field public int32 i

    .field public bool j

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

    {

    ldarg.0

    call       instance void [mscorlib]System.Object::.ctor()

    ldarg.0

    ldfld      int32 yyy::i

    call       void [mscorlib]System.Console::WriteLine(int32)

    ldarg.0

    ldfld bool yyy::j

    call void [mscorlib]System.Console::WriteLine(bool)

    ret

    }

    }

     

    Output

    0

    False

     

           这里,变量ij没有被初始化。因此,这些字段没有在类yyy的静态函数中被初始化。在类yyy的任何代码被调用之前,这些变量会分派到它们的默认值,它们依赖于它们的数据类型。在这个例子中,它们是通过intbool类的构造函数来实现的,因为这些构造函数会首先被调用。

    a.cs

    class zzz

    {

    public static void Main()

    {

    int i = 10;

    string j;

    j = i >= 20 ? "hi" : "bye";

    System.Console.WriteLine(j);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (int32 V_0,class System.String V_1)

    ldc.i4.s   10

    stloc.0

    ldloc.0

    ldc.i4.s   20

    bge.s      IL_000f

    ldstr      "bye"

    br.s       IL_0014

    IL_000f: ldstr      "hi"

    IL_0014: stloc.1

    ldloc.1

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    ret

    }

    }

     

    Output

    bye

     

    如果把语句压缩到单独的一行中,那么使用三元操作符会更加“壮丽”。在C#中,变量ij在转换到IL时变成了V_0V_1。我们首先将变量V_0初始化为10,随后把条件值20放到栈上。

    如果条件为TRUE,那么bge.s就会执行到标号IL_0014的跳转。

    如果条件为FALSE,那么程序就会进行到标号IL_000f

     

    然后,程序进行到WriteLine函数,并打印出相应的文本。

    从最终的IL代码中,无法解释原始的C#代码是否使用一个if语句或?操作符。C#中的大量操作符,例如三元操作符,都是从C程序语言中借用过来的。

    a.cs

    class zzz

    {

    public static void Main()

    {

    int i = 1, j= 2; 

    if ( i >= 4 & j > 1)

    System.Console.WriteLine("& true");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (int32 V_0,int32 V_1)

    ldc.i4.1

    stloc.0

    ldc.i4.2

    stloc.1

    ldloc.0

    ldc.i4.4

    clt

    ldc.i4.0

    ceq

    ldloc.1

    ldc.i4.1

    cgt

    and

    brfalse.s IL_001c

    ldstr      "& true"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    IL_001c: ret

    }

    }

     

    C#中的&操作符使if更加复杂。如果条件都是TRUE,那么它就只返回TRUE;否则,它就返回FALSE。在IL中没有&操作符的等价物。因此,会以一种间接方式来实现它,如下所示:

    首先我们使用ldc指令来把一个常量值当到栈上。

    接下来,stloc指令初始化变量i和j,即V_0和V_1。

    然后,V_0的值被放在栈上。

    之后,检查条件的值4。

    然后,条件clt用来检查栈上的第1个项是否小于第2个。如果是,正如在上面的示例那样,值1(TRUE)就会被放到栈上。

    C#中的原始表达式是i >= 4。在IL中,会使用<或clt。

    然后我们使用ceq检查相等性,即=,并把0放到栈上。结果为FALSE

    然后我们对j > 1采用相同的规则。这里,我们使用cgt而不是cltcgt操作符的结果是TRUE

    这个结果TRUE和前面的结果FALSE进行AND位运算,最后得到一个FALSE值。

     

    注意到AND指令将返回1,当且仅当这两个条件都是TURE。在所有其它的条件中,它将会返回FLASE

    a.cs

    class zzz

    {

    public static void Main()

    {

    int i = 1, j= 2; 

    if ( i >= 4 && j > 1)

    System.Console.WriteLine("&& true");

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (int32 V_0,int32 V_1)

    ldc.i4.1

    stloc.0

    ldc.i4.2

    stloc.1

    ldloc.0

    ldc.i4.4

    blt.s      IL_0016

    ldloc.1

    ldc.i4.1

    ble.s      IL_0016

    ldstr      "&& true"

    call       void [mscorlib]System.Console::WriteLine(class System.String)

    IL_0016: ret

    }

    }

     

    &&这样的操作符被称为短路运算符,因为它们只有当第一个条件为True时才会执行第2个条件。我们重复了和先前一样的IL代码,但是现在条件是使用blt.s指令进行检查的,它是cltbrtrue指令的组合。

           如果条件为FALSE,就会跳转到标号IL_0016处的ret指令。只有当条件为TRUE时,我们就可以向下进行并检查第2个条件。为此,我们使用ble.s指令,它是cgtbrfalse的组合。如果第2个条件为FALSE,我们就像前面一样跳转到ret指令,如果为TRUE,我们就执行WriteLine函数。

           &&操作符执行比&快,因为它只能当第一个条件为TRUE时才会进行到下一步。这样做,第一个表达式的输出就会影响到最后的输出。

    |||操作符也以类似的方式来表现。

    a.cs

    class zzz

    {

    public static void Main()

    {

    bool x,y;

    x = true;

    y = false;

    System.Console.WriteLine( x ^ y);

    x = false;

    System.Console.WriteLine( x ^ y);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (bool V_0,bool V_1)

    ldc.i4.1

    stloc.0

    ldc.i4.0

    stloc.1

    ldloc.0

    ldloc.1

    xor

    call       void [mscorlib]System.Console::WriteLine(bool)

    ldc.i4.0

    stloc.0

    ldloc.0

    ldloc.1

    xor

    call       void [mscorlib]System.Console::WriteLine(bool)

    ret

    }

    }

     

    Output

    True

    False

     

    ^符号被称为XOR操作符。XOR就像一个OR语句,但是有一点不同:OR只有当它的一个操作数为TRUE(其它的操作数为FALSE)时才会返回TRUE。即使这两个操作数都是TRUE,它也会返回FALSExor是一个IL指令。

    !=操作符被转换为一组常规的IL指令,即完成一次比较操作,而程序会相应地进入分支。

    a.cs

    class zzz

    {

    public static void Main()

    {

    bool x = true;

    System.Console.WriteLine(!x);

    }

    }

     

    a.il

    .assembly mukhi {}

    .class private auto ansi zzz extends [mscorlib]System.Object

    {

    .method public hidebysig static void vijay() il managed

    {

    .entrypoint

    .locals (bool V_0)

    ldc.i4.1

    stloc.0

    ldloc.0

    ldc.i4.0

    ceq

    call void [mscorlib]System.Console::WriteLine(bool)

    ret

    }

    }

     

    Output

    False

     

    C#中的!操作符会被转换为TRUE或FALSE,反之亦然。在IL中,使用指令ceq。这个指令检查了栈上最后的2个参数。如果它们相同,就返回TRUE;否则,就返回FALSE

    由于变量xTRUE,它会被初始化为1。此后,会检查它是否和0相同。因为它们是不相等的,结果为0FALSE。这个结果会被放到栈上。同样适用的逻辑使xFALSE0将会被放到栈上,并检查它是否和另一个0相等。由于它们是匹配的,所以最后的答案将会是TRUE

  • 相关阅读:
    dig理解dns主备
    Bind的DNS解析设置forward
    DNS服务器的配置与应用: BIND9 的安装与配置
    注意自己的dns设置
    /etc/named/named.conf.options中的Options参数
    安装Bind过程中提示丢失MSVCR110.dll的解决办法
    MS14-025引起的问题
    MS14-025引起的问题
    MS14-082引起的问题
    WSUS更新服务器
  • 原文地址:https://www.cnblogs.com/Jax/p/1495013.html
Copyright © 2020-2023  润新知