c#代码似乎以无效方式进行优化,使得对象值变为null

我有以下代码,表现出一个奇怪的问题:

var all = new FeatureService().FindAll(); System.Diagnostics.Debug.Assert(all != null, "FindAll must not return null"); System.Diagnostics.Debug.WriteLine(all.ToString()); // throws NullReferenceException 

FindAll方法的签名是:

 public List FindAll() 

单步执行代码我已确认FindAll的返回值不为null,正如您从Assert中看到的那样,“all”变量不为null,但在下一行中它似乎为null。

调用ToString()方法时,问题不是特定于失败。 在尝试跟踪根本原因时,我将其简化为可重现的示例。

这可能是一个线索:在调试器中,变量“all”出现在Locals窗口中,其值为“无法获取本地或参数的值’all’,因为它在此指令指针处不可用,可能是因为它已经被优化了。“

我考虑尝试其他地方记录的方法之一来禁用代码优化,但这并不能真正解决问题,因为代码的发布版本仍然会得到优化。

我正在使用Visual Studio 2010与.NET 4.0。

有什么想法吗?

更新:根据请求,这是整个方法:

 protected override List GetModels() { var all = new FeatureService().FindAll(); var wr = new WeakReference(all); System.Diagnostics.Debug.Assert(all != null, "FindAll must not return null"); System.Diagnostics.Debug.WriteLine(wr.IsAlive); System.Diagnostics.Debug.WriteLine(all.ToString()); // throws NullReferenceException return all; } 

作为一个仅供参考,原始实施只是:

 protected override List GetModels() { return new FeatureService().FindAll(); } 

我最初在调用方法中遇到了nullexception。 我发布的代码是在跟踪问题一段时间之后。

更新#2:根据要求,这是来自exception的堆栈跟踪:

  at FeatureCrowd.DomainModel.FeatureSearch.GetModels() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.DomainModel\FeatureSearch.cs:line 32 at FeatureCrowd.DomainModel.FeatureSearch.CreateIndex() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.DomainModel\FeatureSearch.cs:line 42 at FeatureCrowd.DomainModel.FeatureService.CreateSearchIndex() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.DomainModel\FeatureService.cs:line 100 at Website.MvcApplication.BuildLuceneIndexThread(Object sender) in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.Website\Global.asax.cs:line 50 at Website.MvcApplication.Application_Start() in C:\Users\Gary\Documents\Visual Studio 2010\Projects\FeatureCrowd\FeatureCrowd.Website\Global.asax.cs:line 61 

在通过TeamViewer查看代码,最后在我自己的机器上下载,编译和运行代码之后,我相信这是C#4.0编译器错误的情况。


在管理将问题简化为一些简单的项目和文件后,我发布了一个带有validation请求的问题。 它在这里可用: 可能的C#4.0编译器错误,其他人可以validation吗?


可能的罪魁祸首不是这种方法:

 protected override List GetModels() { var fs = new FeatureService(); var all = fs.FindAll(); var wr = new WeakReference(all); System.Diagnostics.Debug.Assert(all != null, "FindAll must not return null"); System.Diagnostics.Debug.WriteLine(wr.IsAlive); System.Diagnostics.Debug.WriteLine(all.ToString()); // throws NullReferenceException return all; } 

但是它调用的方法是FeatureService.FindAll:

 public List FindAll() { string key = Cache.GetQueryKey("FindAll"); var value = Cache.Load>(key); if (value == null) { var query = Context.Features; value = query.ToList().Select(x => Map(x)).ToList(); var policy = Cache.GetDefaultCacheItemPolicy(value.Select(x => Cache.GetObjectKey(x.Id.ToString())), true); Cache.Store(key, value, policy); } value = new List(); return value; } 

如果我改变了GetModels中的调用:

 var all = fs.FindAll(); 

对此:

 var all = fs.FindAll().ToList(); // remember, it already returned a list 

然后程序崩溃了ExecutionEngineException


在进行了清理,构建,然后通过Reflector查看编译后的代码之后,输出看起来如何(滚动到重要部分的代码底部):

 public List FindAll() { List value; Func CS$<>9__CachedAnonymousMethodDelegate6 = null; List CS$<>9__CachedAnonymousMethodDelegate7 = null; string key = base.Cache.GetQueryKey("FindAll"); if (base.Cache.Load>(key) == null) { if (CS$<>9__CachedAnonymousMethodDelegate6 == null) { CS$<>9__CachedAnonymousMethodDelegate6 = (Func) delegate (Feature x) { return this.Map(x); }; } value = base.Context.Features.ToList().Select(((Func) CS$<>9__CachedAnonymousMethodDelegate6)).ToList(); if (CS$<>9__CachedAnonymousMethodDelegate7 == null) { CS$<>9__CachedAnonymousMethodDelegate7 = (List) delegate (FeatureModel x) { return base.Cache.GetObjectKey(x.Id.ToString()); }; } Func policy = (Func) base.Cache.GetDefaultCacheItemPolicy(value.Select((Func) CS$<>9__CachedAnonymousMethodDelegate7), true); base.Cache.Store>(key, value, (CacheItemPolicy) policy); } value = new List(); bool CS$1$0000 = (bool) value; return (List) CS$1$0000; } 

注意方法的最后3行,这是它们在代码中的样子:

 value = new List(); return value; 

这是Reflector所说的:

 value = new List(); bool CS$1$0000 = (bool) value; return (List) CS$1$0000; 

它创建列表,然后将其转换为布尔值,然后将其转换回列表并返回它。 最有可能这会导致堆栈问题。

这是IL中的相同方法(仍然通过Reflector),我已经剥离了大部分代码:

 .method public hidebysig instance class [mscorlib]System.Collections.Generic.List`1 FindAll() cil managed { .maxstack 5 .locals init ( [0] string key, [1] class [mscorlib]System.Collections.Generic.List`1 'value', [2] class [System.Data.Entity]System.Data.Objects.ObjectSet`1 query, [3] class [mscorlib]System.Func`2 policy, [4] class [mscorlib]System.Func`2 CS$<>9__CachedAnonymousMethodDelegate6, [5] class [mscorlib]System.Collections.Generic.List`1 CS$<>9__CachedAnonymousMethodDelegate7, [6] bool CS$1$0000, [7] char CS$4$0001) ... L_009f: newobj instance void [mscorlib]System.Collections.Generic.List`1::.ctor() L_00a4: stloc.1 L_00a5: ldloc.1 L_00a6: stloc.s CS$1$0000 L_00a8: br.s L_00aa L_00aa: ldloc.s CS$1$0000 L_00ac: ret } 

这是一个显示调试会话的截屏video ,如果你只想要reflection器输出,请跳到大约2:50。

在Lasse发现FindAll方法生成了错误的IL之后,我又遇到了另一种也产生错误IL的方法 – 我也找到了根本原因和解决方案。

第二种方法中的相关行是:

 var policy = Cache.GetDefaultCacheItemPolicy(dependentKeys, true); 

缓存是我自己的对象。 GetDefaultCacheItemPolicy方法返回System.Runtime.Caching.CacheItemPolicy对象。 但是,生成的IL看起来像这样:

 Func policy = (Func) base.Cache.GetDefaultCacheItemPolicy(dependentKeys, true); 

这里有两个项目。 生成错误IL的方法在一个名为DomainModel的项目中,而Cache对象在Utilities项目中,由第一个项目引用。 第二个项目包含对System.Runtime.Caching的引用,但第一个项目没有。

修复是将System.Runtime.Caching的引用添加到第一个项目。 现在生成的IL看起来正确:

 CacheItemPolicy policy = base.Cache.GetDefaultCacheItemPolicy(dependentKeys, true); 

第一种方法(Lasse在他的回答中发布的)现在也产生了适当的IL。

万岁!

留给子孙后代,这不是问题。

看到我的新答案 。


这就是我所相信的。

与你所说的相反,我认为该程序实际上并没有在发布的任何行中崩溃,而是在它们之后的其中一行上崩溃,而你没有发布。

我相信这是因为我也相信你正在进行Release-build,在这种情况下,两个Debug行都将被删除,因为它们被标记为[Conditional("DEBUG")]属性。

这里的线索是all变量已被优化掉,这应该只在Release-build期间发生,而不是Debug-build。

换句话说,我相信all变量实际上是null ,并且Debug行没有被执行,因为它们没有被编译到程序集中。 调试器尽职尽责地报告all变量不再存在。

请注意,所有这些都应该易于测试。 只需在您发布的两个Debug行中的第一个上放置一个断点即可。 如果断点被击中,我的假设很可能是错误的。 如果没有(并且我将猜测断点符号在运行时显示为空心圆),那么这些行不会被编译到程序集中。

如果您怀疑变量以某种方式被优化掉了,您可以使用WeakReferenceIsAlive属性通过all对象或通过GC.KeepAlive(all) 。 我不确定这是否有用,但值得一试。

另一种可能性,虽然极不可能,但由于某种原因, List.ToString会抛出该exception。 您可以使用.NET Reflector等工具validation这一点,以查看该方法的确切function。