《C# to IL》第三章 選擇和循環

IL中,標號(label)是一個末尾帶有冒號(即:)的名稱。它使咱們可以從代碼的一部分無條件地跳轉到另外一部分。咱們常常在由反編譯器生成的IL代碼中看到這個標號。例如:程序員

IL_0000: ldstr      "hi"編程

IL_0005: call       void [mscorlib]System.Console::WriteLine(class System.String)編程語言

IL_000a: call       void zzz::abc()ide

IL_000f: ret函數

           

在冒號前面的詞就是標號。在下面給出的程序中,咱們在函數abc中建立一個名爲a2的標號。指令br用於隨時跳轉到程序中的任何標號。oop

a.ilspa

.assembly mukhi {}設計

.class private auto ansi zzz extends System.Objectci

{作用域

.method public hidebysig static void vijay() il managed

{

.entrypoint

.locals (int32 V_0,class zzz V_1)

newobj instance void zzz::.ctor()

stloc.1

call int32 zzz::abc()

stloc.0

ldloc.0

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

ret

}

.method private hidebysig static int32 abc() il managed

{

.locals (int32 V_0)

ldc.i4.s   20

br.s a2

ldc.i4.s   30

a2: ret

}

}

 

Output

20

 

            函數abc示範了這個概念。在這個函數中,代碼繞過了指令ldc.i4.s 30。所以,返回值顯示爲20而不是30。從而,IL使用br指令來無條件地跳躍到代碼的任何部分。(程序集指令br獲取4字節,而在.sr以前的br,即br.s獲取1字節,對於每一個標記爲.s的指令,解釋都是相同的。)

            br指令是IL得以運轉的關鍵組件之一。

a.cs

class zzz

{

static bool i = true;

public static void Main()

{

if (i)

System.Console.WriteLine("hi");

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static bool i

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldsfld     bool zzz::i

brfalse.s IL_0011

ldstr      "hi"

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

IL_0011: ret

}

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

{

ldc.i4.1

stsfld     bool zzz::i

ret

}

}

 

Output

hi

 

在咱們的C#程序中,咱們將靜態變量初始化爲true值。

靜態變量,若是它們是字段,就會在靜態構造函數.cctor中被初始化。這會在上面的程序中顯示。

另外一方面,局部變量在它們所在的函數中被初始化。

這裏,讓人吃驚的是,使用ldc指令將值1放置在棧上的靜態構造函數中。即便同時在C#IL中定義了字段i,仍是沒有truefalse這樣的符號。

接下來,使用stsfld將靜態變量i初始化爲值1,儘管變量是布爾類型的。這就證明了IL支持bool數據類型,它不會識別出單詞truefalse。所以,在IL中,布爾值分別只是數字10的別名。

布爾運算符TRUEFALSE是由C#引進的關鍵字,用來使程序員的工做更加輕鬆。因爲IL不直接支持這些關鍵字,因此它會替代地使用數字1或0。

指令ldsfld把靜態變量的值加載到棧上。指令brfalse對棧進行掃描。若是它找到了數字1,它就會將其解釋爲TRUE,而若是它找到了數字0,它就會將其解釋爲FALSE

在這個例子中,它在棧上找到的值是1TRUE,因此它不會跳轉到標號IL_0011。在從C#到IL的轉換中,ildasm使用以IL_開始的名稱來代替標號。

指令brfalse表示「若是FALSE就跳轉到標號」。這不一樣於br,後者老是會致使一個跳轉。從而,brfalse是一個有條件的跳轉指令。

IL中沒有提供if語句功能的指令。C#中的if語句會被轉換爲IL中的轉移(branch)指令。咱們所處的任何彙編器,都沒有像if結構體這樣的高級概念。

能夠看到,咱們剛剛學到的那些知識,對於咱們掌握IL是很是重要的。這將幫助咱們得到——區別關於哪一個概念是IL的一部分而哪些是由編程語言的設計者引進——的能力

尤爲須要注意的是,若是IL不支持某個特性,它就不能用任何.NET編程語言實現。從而,熟悉IL所支持的各類概念的重要性——怎麼強調都不過度。

a.cs

class zzz

{

static bool i = true;

public static void Main()

{

if (i)

System.Console.WriteLine("hi");

else

System.Console.WriteLine("false");

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static bool i

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldsfld bool zzz::i

brfalse.s IL_0013

ldstr "hi"

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

br.s IL_001d

IL_0013: ldstr      "false"

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

IL_001d: ret

}

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

{

ldc.i4.1

stsfld     bool zzz::i

ret

}

}

 

 

Output

hi

 

            在編程語言中,if-else語句是極其容易理解的,可是在IL中它倒是至關使人困惑的。IL檢查棧上的值是1仍是0

若是棧上的值是1,正如這個例子中的那樣,它調用帶有參數hiWriteLine函數,並隨後使用無條件跳轉指令br,跳轉到標號IL_001d

若是棧上的值是0,代碼跳轉到IL_0013,而且WriteLine函數會打印出false

            從而,爲了在IL中實現if-else結構,須要一個有條件跳轉和一個無條件跳轉。若是咱們使用多個if-else語句,那麼IL代碼的複雜度就會動態增長。

            如今,能夠看出編譯器的編寫者的智商了。

a.cs

class zzz

{

public static void Main()

{

}

void abc( bool a)

{

if (a)

{

int i = 0;

}

if ( a)

{

int i = 3;

}

}

}

 

 

a.il

.assembly mukhi {}

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

{

.field private int32 x

.method public hidebysig static void vijay() il managed

{

.entrypoint

ret

}

.method private hidebysig instance void abc(bool a) il managed

{

.locals (int32 V_0,int32 V_1)

ldarg.1

brfalse.s IL_0005

ldc.i4.0

stloc.0

IL_0005: ldarg.1

brfalse.s IL_000a

ldc.i4.3

stloc.1

IL_000a: ret

}

}

 

     C#編程語言就更復雜了。在內部的一組括號中,咱們不能建立以前已經在外部建立的變量。上面的C#程序在語法上是正確的,由於括號都是在同一級別上。

            IL中,會稍微簡單一些。這兩個i會變成兩個單獨的變量V_0V_1。所以,IL不會暴露施加在變量上的任何約束。

a.cs

class zzz

{

static bool i = true;

public static void Main()

{

while (i)

{

System.Console.WriteLine("hi");

}

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static bool i

.method public hidebysig static void vijay() il managed

{

.entrypoint

br.s IL_000c

IL_0002: ldstr      "hi"

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

IL_000c: ldsfld     bool zzz::i

brtrue.s IL_0002

ret

}

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

{

ldc.i4.1

stsfld     bool zzz::i

ret

}

}

 

            當看到反彙編的代碼時,你將理解爲何程序員不以編寫IL代碼來謀生。即便一個簡單的while循環,在轉換爲IL後都會變得驚人的複雜。

            對於一個while結構,會建立一個到標號IL_000c的無條件跳轉,它位於函數的結尾。這裏,它加載靜態變量i的值到棧上。

   下一個指令brtrue,作的事情和指令brfalse所作的正好相反。實現以下:

   l 若是棧頂的值——例如,字段i的值——是1,那麼它會跳轉到標號IL_0002。而後值hi被放到棧上而且WriteLine函數會被調用。

   l 若是棧頂的值是0,那麼程序將跳轉到ret指令。

   上面的程序,正如你所看到的那樣,並不打算中止。它會繼續流動,就像一個起源於一個巨大冰川的水流。

a.cs

class zzz

{

static int i = 2;

public static void Main()

{

i = i + 3;

System.Console.WriteLine(i);

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static int32 i

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldsfld int32 zzz::i

ldc.i4.3

add

stsfld int32 zzz::i

ldsfld int32 zzz::i

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

ret

}

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

{

ldc.i4.2

stsfld     bool zzz::i

ret

}

}

 

Output

5

 

IL沒有操做符用來作兩個數字的加法,而是使用add指令。

add指令須要用來作加法的兩個數字,也就是棧上開始的2個有效的元素。所以,ldsfld指令把靜態變量i的值和常量值3放到棧上。隨後,add指令把它們相加並把結果放到棧上。它還會從棧上移除用來作加法的2個數字。

一旦指令被執行了,IL中的大多數指令就會擺脫棧上的參數,也就是該指令要操做的參數。

使用指令stsfld將靜態變量i初始化爲加法的結果總和。剩下的代碼直接顯示了變量i的值。

            IL中沒有++操做符的等價物。它會被轉換爲指令ldc.i4.1一樣,兩個數字相乘,須要使用mul指令;相減,就使用sub指令,等等。它們在IL中都有等價物。以後的代碼保持不變。

a.cs

class zzz

{

static bool i;

static int j = 19;

public static void Main()

{

i = j > 16;

System.Console.WriteLine(i);

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static bool i

.field private static int32 j

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldsfld     int32 zzz::j

ldc.i4.s   16

cgt

stsfld     bool zzz::i

ldsfld     bool zzz::i

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

ret

}

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

{

ldc.i4.s   19

stsfld int32 zzz::j

ret

}

}

 

Output

True

 

            如今咱們將研究IL如何處理條件操做符。讓咱們考慮C#中的語句j>i16IL首先把值j放到棧上,也就是常量值16的前面。隨後它會調用cgt操做,這是它首次在咱們的源代碼中出現。這個指令檢查棧上的第1個值是否大於第2個。若是是,那麼它會把值1TRUE)放到棧上,不然它會把值0FALSE)放到棧上。這個值隨後被存儲到變量i中。使用WritleLine函數,就會生成布爾值的輸出,從而咱們看到顯示True

            一樣,操做符<被轉換爲clt指令,它會檢查棧上的第1個值是否小於第2個。從而咱們看到IL具備它本身的一套邏輯操做符,對基本的邏輯運算進行內部處理。

a.cs

class zzz

{

static bool i;

static int j = 19;

public static void Main()

{

i = j == 16;

System.Console.WriteLine(i);

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static bool i

.field private static int32 j

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldsfld     int32 zzz::j

ldc.i4.s   16

ceq

stsfld     bool zzz::i

ldsfld     bool zzz::i

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

ret

}

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

{

ldc.i4.s   19

stsfld int32 zzz::j

ret

}

}

 

Output

False

 

操做符==就是EQUALITY操做符。它也須要棧上的2個操做數(operand)來檢查相等性。此後它使用ceq指令來檢查相等性。若是相等,它會把值1(TRUE)放到棧上,若是不相等,它會把值0(FALSE)放到棧上。指令ceq是IL的邏輯指令集的不可缺乏的一部分。

a.cs

class zzz

{

static bool i;

static int j = 19;

public static void Main()

{

i = j >= 16;

System.Console.WriteLine(i);

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static bool i

.field private static int32 j

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldsfld     int32 zzz::j

ldc.i4.s   16

cgt

ldc.i4.0

ceq

stsfld     bool zzz::i

ldsfld     bool zzz::i

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

ret

}

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

{

ldc.i4.s   19

stsfld int32 zzz::j

ret

}

}

 

Output

False

 

「小於等於」(<=)和「大於等於」(>=)的實現有一點複雜。實際上它們都具備2個融合在一塊兒的條件。

>=的狀況中,IL首先使用cgt指令來檢查第1個數字是否大於第2個數字。若是是,它將返回值1,不然就返回值0。若是第1個條件是FALSE,那麼ceq指令將會檢查這2個數字是否相等。若是相等,它就返回TRUE,不然就返回FALSE。

讓咱們從一個稍微不一樣的角度來分析上面的IL代碼。咱們對這兩個值1916進行比較。在這個例子中,由於19大於16,因此指令cgt將會把值1放到棧上

讓咱們在靜態構造函數中將字段j的值修改成1。如今,因爲數字1不大於16,因此cgt指令將把值FALSE0放到棧上。此後,使用ldc指令把另外一個0放到棧上。如今當ceq指令比較兩個值時,由於它們都是0,因此它會返回TRUE

如今,若是咱們將j修改成16cgt指令將返回FALSE,由於16不大於16。此後,因爲使用ldc指令把0放到棧上,這兩個傳遞到ceq的值將都是0。因爲00是相等的,因此返回值是1TRUE

若是你不能理解上面的解釋,那麼就從源代碼中移除ldc.i4.0ceq這兩行,並觀察輸出。

a.cs

class zzz

{

static bool i;

static int j = 19;

public static void Main()

{

i = j != 16;

System.Console.WriteLine(i);

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static bool i

.field private static int32 j

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldsfld     int32 zzz::j

ldc.i4.s   16

ceq

ldc.i4.0

ceq

stsfld     bool zzz::i

ldsfld     bool zzz::i

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

ret

}

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

{

ldc.i4.s   19

stsfld int32 zzz::j

ret

}

}

 

Output

True

 

            「不等於」操做符,也就是!=,是==的相反操做。它使用了兩個ceq指令。第1ceq指令用來檢查棧上的值是否相等。若是它們是相等的,它就會返回TRUE;不然就返回FALSE

            2ceq將前面的ceq結果和FLASE進行比較。若是第1ceq的結果是TRUE,那麼最後的答案就是FALSE,反之亦然。

            這確實是一種首創的方式來對一個值求否。

a.cs

class zzz

{

static int i = 1;

public static void Main()

{

while ( i <= 2)

{

System.Console.WriteLine(i);

i++;

}

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static int32 i

.method public hidebysig static void vijay() il managed

{

.entrypoint

br.s IL_0018

IL_0002: ldsfld     int32 zzz::i

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

ldsfld int32 zzz::i

ldc.i4.1

add

stsfld int32 zzz::i

IL_0018: ldsfld int32 zzz::i

ldc.i4.2

ble.s IL_0002

ret

}

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

{

ldc.i4.s   1

stsfld int32 zzz::i

ret

}

}

 

Output

1

2

 

在介紹完條件語句以後,咱們如今將關注於while循環。這種轉換是必須的,由於咱們在諸如while這樣的循環中使用條件語句。包括條件的while循環稍微有點複雜。

讓咱們直接到標號IL_0018上,它位於IL代碼中zzz函數的結尾。這裏存在着一個條件。i的值(即1)被存儲到棧上。接下來,常量2被放到棧上。

若是你再次訪問C#代碼,那麼while語句中的條件就是i <= 2。指令ble.s是基於兩個構造函數的:cgtbrfalse。這個指令檢查了第1個值(即變量i)是否小於等於第2個值。若是是,它就會指示程序跳轉到標號IL_0002。若是不是,程序就移動到下一個指令。

所以,像ble這樣的指令使咱們的工做更加容易,由於咱們沒必要再次使用cgtbrfalse指令。

C#中,while結構的條件出如今頂部,可是條件的代碼出如今底部。在轉換爲IL時,在while結構體之間被執行的代碼,會被放置在條件代碼之上。

a.cs

class zzz

{

static int i = 1;

public static void Main()

{

for ( i = 1; i <= 2 ; i++)

{

System.Console.WriteLine(i);

}

}

}

 

a.il

.assembly mukhi {}

.class private auto ansi zzz extends System.Object

{

.field private static int32 i

.method public hidebysig static void vijay() il managed

{

.entrypoint

ldc.i4.1

stsfld     int32 zzz::i

br.s       IL_001e

IL_0008: ldsfld     int32 zzz::i

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

ldsfld     int32 zzz::i

ldc.i4.1

add

stsfld int32 zzz::i

IL_001e: ldsfld     int32 zzz::i

ldc.i4.2

ble.s IL_0008

ret

}

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

{

ldc.i4.s   1

stsfld int32 zzz::i

ret

}

}

 

Output

1

2

 

老生常談,whilefor結構提供了相同的功能,能夠互相轉換。

for循環中,第1個分號以前的代碼只能被執行一次。所以,將要被初始化的變量i,位於循環的外面。而後,咱們無條件跳轉到標號IL_001e,以檢查i的值是否小於2。若是結果爲TRUE,那麼代碼跳轉到標號IL_0008,它是for語句這段代碼的開始點。

使用WriteLine函數打印出i的值。此後,變量i的值每次增加1,而條件會被一次又一次的檢查。

a.cs

public class zzz

{

public static void Main()

{

int i;

i = 1;

while ( i <= 2)

{

System.Console.Write(i);

i++;

}

i = 1;

do

{

System.Console.Write(i);

i++;

} while ( i <= 2);

}

}

 

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)

ldc.i4.1

stloc.0

br.s       IL_000e

IL_0004:  ldloc.0

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

ldloc.0

ldc.i4.1

add

stloc.0

IL_000e: ldloc.0

ldc.i4.2

ble.s      IL_0004

ldc.i4.1

stloc.0

IL_0014: ldloc.0

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

ldloc.0

ldc.i4.1

add

stloc.0

ldloc.0

ldc.i4.2

ble.s      IL_0014

ret

}

}

 

Output

1212

 

            C#中,do循環和while循環之間的區別是——條件在什麼位置被檢查。

do-while循環中,條件會在循環的結尾被檢查。這意味着循環中的代碼至少會被調用一次。

while循環中,條件會在循環的開始被檢查。所以,代碼可能歷來都不會被執行。

 

不論是哪一種狀況,咱們都把值1放到棧上,並初始化變量iV_1

while循環中,咱們首先跳轉到標號IL_000e,也就是檢查條件——變量是否小於等於2——的地方。若是返回TRUE,咱們就跳轉到標號IL_0004

do-while循環中,首先Write函數會被執行,隨後包括在花括號{}中的剩餘代碼將會被執行。當到達花括號{}中代碼的最後一行時,條件纔會被檢查。

 

所以,在IL中寫一個do-while循環要比寫一個while循環簡單得多,由於條件會直接在循環的末尾被檢查。

a.cs

public class zzz

{

public static void Main()

{

int i ;

for ( i = 1; i<= 10 ; i++)

{

if ( i == 2)

break;

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

.locals (int32 V_0)

ldc.i4.1

stloc.0

br.s       IL_0014

IL_0004: ldloc.0

ldc.i4.2

bne.un.s   IL_000a

br.s       IL_0019

IL_000a: ldloc.0

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

ldloc.0

ldc.i4.1

add

stloc.0

IL_0014: ldloc.0

ldc.i4.s   10

ble.s      IL_0004

IL_0019:  ret

}

}

 

Output

1

 

            break語句強迫退出for循環、while循環或do-while循環。和往常同樣,咱們跳轉到標號IL_0014,也就是變量V_0i的值被放置到棧上的地方。而後,咱們把條件值10放到棧上,並使用ble.s指令檢查i是小於仍是大於10。

  • 若是小於,咱們就進入到循環中的標號 IL_0004 。咱們再次把變量 i的值放到棧上,並把if語句的值2放到棧上。而後,咱們使用 bne 指令——它是 ceq 指令和 brfalse 指令的組合。
  • 若是變量 V_0 TRUE break 語句就能確保——使用 br.s 指令跳轉到標號 IL_0019 處的 ret 語句從而退出 loop 循環。

a.cs

public class zzz

{

public static void Main()

{

int i ;

for ( i = 1; i<= 10 ; i++)

{

if ( i == 2)

continue;

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

.locals (int32 V_0)

ldc.i4.1

stloc.0

br.s       IL_0014

IL_0004: ldloc.0

ldc.i4.2

bne.un.s   IL_000a

br.s       IL_0010

IL_000a: ldloc.0

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

IL_0010: ldloc.0

ldc.i4.1

add

stloc.0

IL_0014: ldloc.0

ldc.i4.s   10

ble.s      IL_0004

ret

}

}

 

      continue語句控制for循環到達結束位置。當if語句結果爲TRUE時,程序將繞過WriteLine函數而跳轉到循環的結束。而後,代碼將在標號IL_0010繼續執行,這裏,變量V_0的值會增長1。

            breakcontinue語句之間的主要區別以下所示:

break語句中,程序會跳出循環。

continue語句中,程序繞過剩下的語句,跳轉到循環的結尾。

 

goto語句也能到達相同的功能。從而,break語句、continue語句或goto語句,在轉換IL時,都會被轉換爲相同的br指令。

下面的程序示範了C#中的goto語句會被直接轉換爲IL中的br語句。

a.cs

public class zzz

{

public static void Main()

{

goto aa;

aa: ;

}

}

 

 

a.il

.assembly mukhi {}

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

{

.method public hidebysig static void vijay() il managed

{

.entrypoint

br.s       IL_0002

IL_0002: ret

}

}

 

C#中一個簡單的goto語句,會被轉換爲IL中的br指令。在C#這樣的語言中使用goto被認爲是不恰當的,可是,它在IL中的等價物——br指令,對於實現諸如if語句、循環等各類結構而言,倒是極其有用的。所以,在編程語言中的禁忌,在IL中倒是極其有用的。

a.cs

public class zzz

{

public static void Main()

{

int j;

for ( int i = 1; i <= 2 ; i++)

System.Console.Write(i);

}

}

 

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.1

br.s       IL_000e

IL_0004: ldloc.1

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

ldloc.1

ldc.i4.1

add

stloc.1

IL_000e: ldloc.1

ldc.i4.2

ble.s      IL_0004

ret

}

}

 

Output

12

 

這個例子解釋了for循環。咱們在Main函數中建立了一個變量j,在for語句中建立了一個變量i。在C#中,這個變量i只在for循環中是可見的。所以,這個變量的做用域是受限制的。

可是,轉換到IL時,全部的變量都具備相同的做用域。這是由於,變量做用域的概念對IL而言有所不一樣。所以,取決於C#編譯器所執行的變量做用域規則。所以,咱們能判斷出——全部的變量在IL中具備相同的做用域和可見性。

相關文章
相關標籤/搜索