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

我们创建了一个接口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#中,有一些类,是我们不能从中派生的 ——Delegate、Enum和ValueType。

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个对象a和b,它们分别是类yyy和xxx的实例。类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个构造函数 (一个是基类的,一个是派生类的)。

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

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

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

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

因此,base和this在IL中是不存在的,它们是编译期间被硬编译到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

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

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#中,变量i和j在转换到IL时变成了V_0和V_1。我们首先将变量V_0初始化为10 ,随后把条件值20放到栈上。

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

l 如果条件为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中没有&操作符的等价物。因此,会以一种间接方式来实现它,如下所示:

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

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

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

l 之后,检查条件的值4。

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

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

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

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

l 这个结果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指令进行检查的,它是clt和brtrue指令的组合。

如果条件为 FALSE,就会跳转到标号IL_0016处的ret指令。只有当条件为TRUE时,我们就可以向下进行并检查第2个条 件。为此,我们使用ble.s指令,它是cgt和brfalse的组合。如果第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,它也会返回FALSE。xor是一个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。

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

类别:.NET技术  来源:HDUT.COM 独特科技  作者:hpping  日期:2014-06-26 22:48

上一条:《C# to IL》第四章 关键字和操作符(上)
下一条:.net中webconfig 详解