警惕值类型的陷阱(补充)

警惕值类型的陷阱中,乌卡卡同学指出第一个示例之所以出现问题,是因为委托的实现方式不对。

我能想到的“委托的实现方式”除了这种正常的赋值,剩下的就是用Lambda表达式或匿名方法了了,于是我把代码改为如下的形式:

struct MyStruct
{
    public int value;
    public void SetValue(int value)
    {
        this.value = value;
    }
}

class Program
{
    static void Main()
    {
        var ms = new MyStruct();
        Action<int> action = (i) => ms.SetValue(i);
        action(1);
        Console.WriteLine(ms.value);
        Console.ReadLine();
    }
}

运行,奇迹发生了,结果是1!

为什么改成Lambda表达式之后结果完全不一样了呢?我本能的反应是没有发生装箱操作,但一时还搞不明白到底是怎么回事,于是迫不及待地打开IL查看:

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       65 (0x41)
  .maxstack  3
  .locals init ([0] class [mscorlib]System.Action`1 action,
           [1] class MyTest.Program/'<>c__DisplayClass1' 'CS$<>8__locals2')
  IL_0000:  newobj     instance void MyTest.Program/'<>c__DisplayClass1'::.ctor()
  IL_0005:  stloc.1
  IL_0006:  nop
  IL_0007:  ldloc.1
  IL_0008:  ldflda     valuetype MyTest.MyStruct MyTest.Program/'<>c__DisplayClass1'::ms
  IL_000d:  initobj    MyTest.MyStruct
  IL_0013:  ldloc.1
  IL_0014:  ldftn      instance void MyTest.Program/'<>c__DisplayClass1'::'
b__0'(int32) IL_001a: newobj instance void class [mscorlib]System.Action`1::.ctor(object, native int) IL_001f: stloc.0 IL_0020: ldloc.0 IL_0021: ldc.i4.1 IL_0022: callvirt instance void class [mscorlib]System.Action`1::Invoke(!0) IL_0027: nop IL_0028: ldloc.1 IL_0029: ldflda valuetype MyTest.MyStruct MyTest.Program/'<>c__DisplayClass1'::ms IL_002e: ldfld int32 MyTest.MyStruct::'value' IL_0033: call void [mscorlib]System.Console::WriteLine(int32) IL_0038: nop IL_0039: call string [mscorlib]System.Console::ReadLine() IL_003e: pop IL_003f: nop IL_0040: ret } // end of method Program::Main

MyTest.MyStruct MyTest.Program/'<>c__DisplayClass1'是编译器为Lambda表达式自动生成的类。因为Lambda表达式也好,匿名方法也好,都需要存在于类之中。上面的代码与下面是一样的:

Action<int> action = delegate(int i) { ms.SetValue(i); };

看到这里,我想您应该已经明白了。这里用于委托_target字段的是匿名方法所在的类<>c__DisplayClass1,而不是装箱之后的MyStruct,因此不存在装箱操作。我们只是在匿名方法内部调用了SetValue方法,并把该匿名方法作为委托的方法,而不是SetValue方法。也就是说委托的Invoke调用的是匿名方法,而不是SetValue方法。

我想这就应该是乌卡卡同学所指出的,换一种委托形式以避免装箱的方法吧。

posted @ 2009-12-10 10:53  麒麟.NET  阅读(1822)  评论(17编辑  收藏  举报