none
为什么Contract.Assert(a==null),当a为null时也会抛异常? RRS feed

  • 问题

  • 先看两幅图

    1

    2

    c出问题的方法是在.net remoting中调用的,所以不是wpf控件所在的线程。


    问题怪就怪在如果保留wpf调用,xz会从有值的状态变成null。然后它虽然是null了,第一个Contract.Assert(xz!=null)却不抛异常,第二个Assert(xz==null)才抛异常,似乎xz的真实值和“局部变量”窗口里显示的不一样。

    如果把wpf调用的话注释掉,那就正常了。

    求解惑!!


    2012年7月2日 10:03

答案

  • 这里有两段方法的实现,其实第一段我们不需要关系,是第一个lambda表达式的方法,我们关注第二段。可以看到,在处理闭包的时候,编译器生成了一个xz字段提供给外部的代码来进行赋值。为什么会有两个xz局部变量,其中一个就是这里的lambda被编译器生成的内部类的字段。

    而你已经离问题根源很近了,你需要看一下反射的代码中,有没有为这个字段赋值的代码,是如何赋值的,也就是外部在调用此lambda时候是什么时候为此闭包结构中的xz赋值的。最后是如何处理闭包结构中的xz字段的。

    我们注意到,这两个xz在同一个当前断言执行的线程中分别可能为一个右值和左值,我们会在调用lambda之前 对  

    c__DisplayClassc.xz = xz;

    赋值。最初出现问题的原因可能在于,断言所访问的当前执行线程中c__DisplayClassc.xz字段值已经被设为null,因为此时lambda已经执行完毕,而断言失败;但同时原来的xz因为为右值,赋完值之后左值得变化并不会对其受到影响,所以你在调试好时候还是看到了他的存在。


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月9日 9:28
    版主
  • IL中一开始的本地量声明中就有了两个 'Pyxd_行动选择' , 一个是 [2] class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' CS$1$0000,  另一个是 [5] class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' 'Contract.Result()', 编号为 [2]和[5]

    下面关键部分的IL从

      IL_009a:  ldloc.1
      IL_009b:  newobj     instance void [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择'::.ctor()
      IL_00a0:  stfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::xz

    开始。线程栈载入本地变量[1] 即c__DisplayClassc实例, 调用'Pyxd_行动选择'构造函数构造,stfld 将值设置到本地变量[1]字段 '<>c__DisplayClassc'::xz 中。下面就是WaitHandle的代码,省略。

    然后在最后Invoke结束,

      IL_0108:  ldloc.1
      IL_0109:  ldfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::xz
      IL_010e:  stloc.2

    调入本地变量[1],调入字段 <>c__DisplayClassc'::xz 值进入栈,将 <>c__DisplayClassc'::xz 值存储到 本地变量[2]中。

      IL_0114:  ldloc.2
      IL_0115:  stloc.s    'Contract.Result()'
      IL_0117:  br         IL_011c
      IL_011c:  ldloc.s    'Contract.Result()'

    将本地变量[2]传到 'Contract.Result()' 变量,进行返回。

    所以上下文中只有最后Invoke之后对本地变量 xz 进行赋值,之前没有。

      var xz = new Pyxd_行动选择();
      ....
      return xz;

    这里var xz = new ...() 并没有将实例存储到xz中,而是如 c__DisplayClassc.xz = xz; 表达式,xz作为右值存在,直接存储到c__DisplayClassc.xz 字段中。

    你是否开启了代码优化或者用了Release模式。不过从IL代码分析结果,这个是完全正确的。


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月11日 6:48
    版主

全部回复

  • 就我所知而言,应该不会异常的。

    不过看你代码,貌似你初始化了xz,然后我无法找到你把xz设置为null的语句。建议你在异步执行完毕之后(使用Dispatcher事件或者啥的),尝试把xz设置为null看看。谢谢!


    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处

    2012年7月3日 2:59
  • 我改了好久,发现如果

    Dispatcher.Invoke(new Action<行动选择>(_xz=>

    {

    ...

    }),xz)

    这样就不会异常了。太奇怪了啊。

    我知道如果在循环里,外界把xz置为null,那么闭包(?)里的值就会变。问题是这里根本没有循环啊。

    2012年7月3日 5:46
  • 能否反出这两者的IL出来看一些,对比下。

    闭包的结构中,为了捕获外部变量,编译器会构造一个类型,为每一个闭包所捕获的变量创建一个公有字段,你在IL中可以看到。至于这个值是否有改变,我们在IL下进行研究会比较直观。


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月4日 2:57
    版主
  • 其实第一幅图已经显示出奇怪的地方了,局部变量列表里显示有两个xz!!!我当时没注意。

    我用Reflector看这个方法,结果Reflector抛异常了!!我是不是发现防Reflector的新方法了呀。。

    IL反射结果马上来。。


    2012年7月7日 11:44
  • 我给出这个方法的所有代码,方便跟IL对照。注意我删除了最后两句Contract.Assert,稍微简化一下。

            public Pyxd_行动选择 Pycp_回应阶段选择(IPyjn_技能[] jns, ICard[] handCards)
            {
                Contract.Requires(Thread.CurrentThread != Dispatcher.Thread);
    
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    var equalityComparer = EqualityComparer<ICard>.Default;
                    for (int i = 0; i < HandCardList.Items.Count; i++)
                    {
                        ListBoxItem listBoxItem = ((ListBoxItem)HandCardList.ItemContainerGenerator.ContainerFromIndex(i));
    
                        listBoxItem.IsEnabled = handCards.Contains(HandCardList.Items[i] as ICard, equalityComparer);
                    }
                    OkButton.IsEnabled = true;
                    CancelButton.IsEnabled = true;
                    CardButtonTab.IsSelected = true;
                }));
    
    
                var xz = new Pyxd_行动选择();
                if (WaitHandle.WaitAny(new[] { waitOkHandle, waitCancelHandle }) == 0)
                {
                    Dispatcher.Invoke(new Action(() =>
                        {
                            xz.HandCard = (IPydj_对将牌) HandCardList.SelectedItem;
                        }));
                }
                return xz;
            }

    然后是IL

    .method public hidebysig instance class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' 
            'Pycp_回应阶段选择'(class [TranscendentalKill]Gqqnbig.TranscendentalKill.Talents.'IPyjn_技能'[] jns,
                                      class [TranscendentalKill]Gqqnbig.TranscendentalKill.Cards.ICard[] handCards) cil managed
    {
      // 代码大小       287 (0x11f)
      .maxstack  11
      .locals init ([0] class [mscorlib]System.Action V_0,
               [1] class Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc' V_1,
               [2] class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' CS$1$0000,
               [3] class [mscorlib]System.Threading.AutoResetEvent[] CS$0$0001,
               [4] bool CS$4$0002,
               [5] class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' 'Contract.Result()',
               [6] class [mscorlib]System.Action 'CS$<>9__CachedAnonymousMethodDelegateb',
               [7] class Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc' 'CS$<>8__localsd')
      IL_0000:  ldnull
      IL_0001:  stloc.s    'CS$<>9__CachedAnonymousMethodDelegateb'
      IL_0003:  newobj     instance void Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::.ctor()
      IL_0008:  stloc.s    'CS$<>8__localsd'
      IL_000a:  ldloc.s    'CS$<>8__localsd'
      IL_000c:  ldarg.2
      IL_000d:  stfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.Cards.ICard[] Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::handCards
      IL_0012:  ldloc.s    'CS$<>8__localsd'
      IL_0014:  ldarg.0
      IL_0015:  stfld      class Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::'<>4__this'
      IL_001a:  nop
      IL_001b:  ldsfld     int32 System.Diagnostics.Contracts.__ContractsRuntime::insideContractEvaluation
      IL_0020:  ldc.i4.4
      IL_0021:  bgt        IL_0065
      .try
      {
        IL_0026:  ldsfld     int32 System.Diagnostics.Contracts.__ContractsRuntime::insideContractEvaluation
        IL_002b:  ldc.i4.1
        IL_002c:  add
        IL_002d:  stsfld     int32 System.Diagnostics.Contracts.__ContractsRuntime::insideContractEvaluation
        IL_0032:  call       class [mscorlib]System.Threading.Thread [mscorlib]System.Threading.Thread::get_CurrentThread()
        IL_0037:  ldarg.0
        IL_0038:  call       instance class [WindowsBase]System.Windows.Threading.Dispatcher [WindowsBase]System.Windows.Threading.DispatcherObject::get_Dispatcher()
        IL_003d:  callvirt   instance class [mscorlib]System.Threading.Thread [WindowsBase]System.Windows.Threading.Dispatcher::get_Thread()
        IL_0042:  ceq
        IL_0044:  ldc.i4.0
        IL_0045:  ceq
        IL_0047:  ldnull
        IL_0048:  ldstr      "Thread.CurrentThread != Dispatcher.Thread"
        IL_004d:  call       void System.Diagnostics.Contracts.__ContractsRuntime::Requires(bool,
                                                                                            string,
                                                                                            string)
        IL_0052:  nop
        IL_0053:  leave      IL_0065
      }  // end .try
      finally
      {
        IL_0058:  ldsfld     int32 System.Diagnostics.Contracts.__ContractsRuntime::insideContractEvaluation
        IL_005d:  ldc.i4.1
        IL_005e:  sub
        IL_005f:  stsfld     int32 System.Diagnostics.Contracts.__ContractsRuntime::insideContractEvaluation
        IL_0064:  endfinally
      }  // end handler
      IL_0065:  ldnull
      IL_0066:  stloc.0
      IL_0067:  newobj     instance void Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::.ctor()
      IL_006c:  stloc.1
      IL_006d:  ldloc.1
      IL_006e:  ldarg.2
      IL_006f:  stfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.Cards.ICard[] Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::handCards
      IL_0074:  ldloc.1
      IL_0075:  ldarg.0
      IL_0076:  stfld      class Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::'<>4__this'
      IL_007b:  nop
      IL_007c:  ldarg.0
      IL_007d:  call       instance class [WindowsBase]System.Windows.Threading.Dispatcher [WindowsBase]System.Windows.Threading.DispatcherObject::get_Dispatcher()
      IL_0082:  ldloc.1
      IL_0083:  ldftn      instance void Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::'<Pycp_回应阶段选择>b__9'()
      IL_0089:  newobj     instance void [mscorlib]System.Action::.ctor(object,
                                                                        native int)
      IL_008e:  ldc.i4.0
      IL_008f:  newarr     [mscorlib]System.Object
      IL_0094:  callvirt   instance class [WindowsBase]System.Windows.Threading.DispatcherOperation [WindowsBase]System.Windows.Threading.Dispatcher::BeginInvoke(class [mscorlib]System.Delegate,
                                                                                                                                                                  object[])
      IL_0099:  pop
      IL_009a:  ldloc.1
      IL_009b:  newobj     instance void [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择'::.ctor()
      IL_00a0:  stfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::xz
      IL_00a5:  ldc.i4.2
      IL_00a6:  newarr     [mscorlib]System.Threading.AutoResetEvent
      IL_00ab:  stloc.3
      IL_00ac:  ldloc.3
      IL_00ad:  ldc.i4.0
      IL_00ae:  ldarg.0
      IL_00af:  ldfld      class [mscorlib]System.Threading.AutoResetEvent Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox::waitOkHandle
      IL_00b4:  stelem     [mscorlib]System.Threading.AutoResetEvent
      IL_00b9:  ldloc.3
      IL_00ba:  ldc.i4.1
      IL_00bb:  ldarg.0
      IL_00bc:  ldfld      class [mscorlib]System.Threading.AutoResetEvent Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox::waitCancelHandle
      IL_00c1:  stelem     [mscorlib]System.Threading.AutoResetEvent
      IL_00c6:  ldloc.3
      IL_00c7:  call       int32 [mscorlib]System.Threading.WaitHandle::WaitAny(class [mscorlib]System.Threading.WaitHandle[])
      IL_00cc:  ldc.i4.0
      IL_00cd:  ceq
      IL_00cf:  ldc.i4.0
      IL_00d0:  ceq
      IL_00d2:  stloc.s    CS$4$0002
      IL_00d4:  ldloc.s    CS$4$0002
      IL_00d6:  brtrue     IL_0108
      IL_00db:  nop
      IL_00dc:  ldarg.0
      IL_00dd:  call       instance class [WindowsBase]System.Windows.Threading.Dispatcher [WindowsBase]System.Windows.Threading.DispatcherObject::get_Dispatcher()
      IL_00e2:  ldloc.0
      IL_00e3:  brtrue     IL_00fa
      IL_00e8:  ldloc.1
      IL_00e9:  ldftn      instance void Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::'<Pycp_回应阶段选择>b__a'()
      IL_00ef:  newobj     instance void [mscorlib]System.Action::.ctor(object,
                                                                        native int)
      IL_00f4:  stloc.0
      IL_00f5:  br         IL_00fa
      IL_00fa:  ldloc.0
      IL_00fb:  ldc.i4.0
      IL_00fc:  newarr     [mscorlib]System.Object
      IL_0101:  callvirt   instance object [WindowsBase]System.Windows.Threading.Dispatcher::Invoke(class [mscorlib]System.Delegate,
                                                                                                    object[])
      IL_0106:  pop
      IL_0107:  nop
      IL_0108:  ldloc.1
      IL_0109:  ldfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::xz
      IL_010e:  stloc.2
      IL_010f:  br         IL_0114
      IL_0114:  ldloc.2
      IL_0115:  stloc.s    'Contract.Result()'
      IL_0117:  br         IL_011c
      IL_011c:  ldloc.s    'Contract.Result()'
      IL_011e:  ret
    } // end of method MyAvatarBox::'Pycp_回应阶段选择'



    2012年7月7日 11:48
  • 我再给出编译器生成的内部类。这是Reflector给出的代码,我想应该比IL更舒服一点。

    [CompilerGenerated]
    private sealed class <>c__DisplayClassc
    {
        // Fields
        public MyAvatarBox <>4__this;
        public ICard[] handCards;
        public Pyxd_行动选择 xz;
    
        // Methods
        public void <Pycp_回应阶段选择>b__9()
        {
            EqualityComparer<ICard> equalityComparer = EqualityComparer<ICard>.Default;
            for (int i = 0; i < this.<>4__this.HandCardList.Items.Count; i++)
            {
                ListBoxItem listBoxItem = (ListBoxItem) this.<>4__this.HandCardList.ItemContainerGenerator.ContainerFromIndex(i);
                listBoxItem.IsEnabled = this.handCards.Contains<ICard>(this.<>4__this.HandCardList.Items[i] as ICard, equalityComparer);
            }
            this.<>4__this.OkButton.IsEnabled = true;
            this.<>4__this.CancelButton.IsEnabled = true;
            this.<>4__this.CardButtonTab.IsSelected = true;
        }
    
        public void <Pycp_回应阶段选择>b__a()
        {
            this.xz.HandCard = (IPydj_对将牌) this.<>4__this.HandCardList.SelectedItem;
        }
    }
    
     
    Collapse Methods
    

    2012年7月7日 11:58
  • 这里有两段方法的实现,其实第一段我们不需要关系,是第一个lambda表达式的方法,我们关注第二段。可以看到,在处理闭包的时候,编译器生成了一个xz字段提供给外部的代码来进行赋值。为什么会有两个xz局部变量,其中一个就是这里的lambda被编译器生成的内部类的字段。

    而你已经离问题根源很近了,你需要看一下反射的代码中,有没有为这个字段赋值的代码,是如何赋值的,也就是外部在调用此lambda时候是什么时候为此闭包结构中的xz赋值的。最后是如何处理闭包结构中的xz字段的。

    我们注意到,这两个xz在同一个当前断言执行的线程中分别可能为一个右值和左值,我们会在调用lambda之前 对  

    c__DisplayClassc.xz = xz;

    赋值。最初出现问题的原因可能在于,断言所访问的当前执行线程中c__DisplayClassc.xz字段值已经被设为null,因为此时lambda已经执行完毕,而断言失败;但同时原来的xz因为为右值,赋完值之后左值得变化并不会对其受到影响,所以你在调试好时候还是看到了他的存在。


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月9日 9:28
    版主
  • 我看得不是很明白,不过重点似乎是c__DisplayClassc.xz和回应阶段选择方法里的xz的相互关系。

    其实我加Contract.Assert只是为了显露问题,因为“回应阶段选择”的调用方得到的它的返回值总是null。

    我IL看不太懂。大侠您能否指着IL给我说下c__DisplayClassc.xz和回应阶段选择方法里的xz的来龙去脉?谢谢!

    2012年7月10日 5:33
  • IL中一开始的本地量声明中就有了两个 'Pyxd_行动选择' , 一个是 [2] class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' CS$1$0000,  另一个是 [5] class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' 'Contract.Result()', 编号为 [2]和[5]

    下面关键部分的IL从

      IL_009a:  ldloc.1
      IL_009b:  newobj     instance void [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择'::.ctor()
      IL_00a0:  stfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::xz

    开始。线程栈载入本地变量[1] 即c__DisplayClassc实例, 调用'Pyxd_行动选择'构造函数构造,stfld 将值设置到本地变量[1]字段 '<>c__DisplayClassc'::xz 中。下面就是WaitHandle的代码,省略。

    然后在最后Invoke结束,

      IL_0108:  ldloc.1
      IL_0109:  ldfld      class [TranscendentalKill]Gqqnbig.TranscendentalKill.'Pyxd_行动选择' Gqqnbig.TranscendentalKill.UI.Primatives.MyAvatarBox/'<>c__DisplayClassc'::xz
      IL_010e:  stloc.2

    调入本地变量[1],调入字段 <>c__DisplayClassc'::xz 值进入栈,将 <>c__DisplayClassc'::xz 值存储到 本地变量[2]中。

      IL_0114:  ldloc.2
      IL_0115:  stloc.s    'Contract.Result()'
      IL_0117:  br         IL_011c
      IL_011c:  ldloc.s    'Contract.Result()'

    将本地变量[2]传到 'Contract.Result()' 变量,进行返回。

    所以上下文中只有最后Invoke之后对本地变量 xz 进行赋值,之前没有。

      var xz = new Pyxd_行动选择();
      ....
      return xz;

    这里var xz = new ...() 并没有将实例存储到xz中,而是如 c__DisplayClassc.xz = xz; 表达式,xz作为右值存在,直接存储到c__DisplayClassc.xz 字段中。

    你是否开启了代码优化或者用了Release模式。不过从IL代码分析结果,这个是完全正确的。


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月11日 6:48
    版主
  • 局部变量窗体里,你为什么不把“类型”那一列显示出来呢?

    我复制了你的代码,也无法重现你第一幅图的场景,我注意到你图中的代码 return xz; 被置灰了,因此我怀疑你调试的代码与实际运行的程序不一致,你可以重新编译一下整个解决方案后再测试。

    2012年7月11日 9:25
  • 清楚了,非常感谢。是有点奇怪,我已经用Invoke(new Action<Pyxd_行动选择>(xz=>..)的方式来调用了,一切正常。或许有点小bug吧。。

    我没有开Release或代码优化模式。最后return显灰是因为我用了Resharpner,它“启发式”发现两句相反的Contract之后不可能到达return,所以灰显了。

    2012年7月15日 1:36