我有以下代码表现出一个奇怪的问题:
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<FeatureModel> FindAll()
单步执行代码我已经确认FindAll的返回值不为null,正如您从Assert中看到的那样,“all”变量不为null,但在下一行中它似乎为null。 / p>
问题不是特定于调用ToString()方法时失败。在尝试追踪根本原因时,我将其简化为可重复的示例。
这可能是一个线索:在调试器中,变量“all”出现在Locals窗口中,其值为“无法获取本地或参数的值'all',因为它在此指令指针处不可用,可能是因为它已经被优化了。“
我考虑尝试其他方法中的一种方法来禁用代码优化,但这并不能真正解决问题,因为代码的发布版本仍然会得到优化。
我正在使用Visual Studio 2010和.NET 4.0。
有什么想法吗?
更新:根据请求,这是整个方法:
protected override List<FeatureModel> 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<FeatureModel> GetModels() {
return new FeatureService().FindAll();
}
我最初在调用方法中遇到了null异常。我发布的代码是在跟踪问题一段时间之后。
UPDATE#2:根据要求,这是来自异常的堆栈跟踪:
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
答案 0 :(得分:21)
在通过TeamViewer查看代码,最后在我自己的机器上下载,编译和运行代码之后,我相信这是C#4.0中编译器错误的情况
在管理将问题减少到一些简单的项目和文件之后,我发布了一个带有验证请求的问题。它可以在这里找到:Possible C# 4.0 compiler error, can others verify?
可能的罪魁祸首不是这种方法:
protected override List<FeatureModel> 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<FeatureModel> FindAll() {
string key = Cache.GetQueryKey("FindAll");
var value = Cache.Load<List<FeatureModel>>(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<FeatureModel>();
return value;
}
如果我改变了GetModels中的调用:
var all = fs.FindAll();
到此:
var all = fs.FindAll().ToList(); // remember, it already returned a list
然后程序崩溃了ExecutionEngineException
。
在做了一个干净的构建,然后通过Reflector查看编译后的代码之后,输出看起来如何(滚动到重要部分代码的底部):
public List<FeatureModel> FindAll()
{
List<FeatureModel> value;
Func<FeatureModel, string> CS$<>9__CachedAnonymousMethodDelegate6 = null;
List<FeatureModel> CS$<>9__CachedAnonymousMethodDelegate7 = null;
string key = base.Cache.GetQueryKey("FindAll");
if (base.Cache.Load<List<FeatureModel>>(key) == null)
{
if (CS$<>9__CachedAnonymousMethodDelegate6 == null)
{
CS$<>9__CachedAnonymousMethodDelegate6 = (Func<FeatureModel, string>) delegate (Feature x) {
return this.Map(x);
};
}
value = base.Context.Features.ToList<Feature>().Select<Feature, FeatureModel>(((Func<Feature, FeatureModel>) CS$<>9__CachedAnonymousMethodDelegate6)).ToList<FeatureModel>();
if (CS$<>9__CachedAnonymousMethodDelegate7 == null)
{
CS$<>9__CachedAnonymousMethodDelegate7 = (List<FeatureModel>) delegate (FeatureModel x) {
return base.Cache.GetObjectKey(x.Id.ToString());
};
}
Func<Feature, FeatureModel> policy = (Func<Feature, FeatureModel>) base.Cache.GetDefaultCacheItemPolicy(value.Select<FeatureModel, string>((Func<FeatureModel, string>) CS$<>9__CachedAnonymousMethodDelegate7), true);
base.Cache.Store<List<FeatureModel>>(key, value, (CacheItemPolicy) policy);
}
value = new List<FeatureModel>();
bool CS$1$0000 = (bool) value;
return (List<FeatureModel>) CS$1$0000;
}
注意方法的最后3行,这是它们在代码中的样子:
value = new List<FeatureModel>();
return value;
这是Reflector所说的:
value = new List<FeatureModel>();
bool CS$1$0000 = (bool) value;
return (List<FeatureModel>) CS$1$0000;
它创建列表,然后将其转换为布尔值,然后将其强制转换回列表并返回它。很可能这会导致堆栈问题。
这是同样的方法,在IL中(仍然通过Reflector),我已经剥离了大部分代码:
.method public hidebysig instance class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> FindAll() cil managed
{
.maxstack 5
.locals init (
[0] string key,
[1] class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> 'value',
[2] class [System.Data.Entity]System.Data.Objects.ObjectSet`1<class FeatureCrowd.DomainModel.Feature> query,
[3] class [mscorlib]System.Func`2<class FeatureCrowd.DomainModel.Feature, class FeatureCrowd.DomainModel.FeatureModel> policy,
[4] class [mscorlib]System.Func`2<class FeatureCrowd.DomainModel.FeatureModel, string> CS$<>9__CachedAnonymousMethodDelegate6,
[5] class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> CS$<>9__CachedAnonymousMethodDelegate7,
[6] bool CS$1$0000,
[7] char CS$4$0001)
...
L_009f: newobj instance void [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel>::.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
}
这是一个screencast showing the debug session,如果你只想要反射器输出,请跳到大约2:50。
答案 1 :(得分:8)
在Lasse发现FindAll方法生成了错误的IL之后,我又遇到了另一种产生错误IL的方法 - 我也找到了根本原因和解决方案。
第二种方法中的相关行是:
var policy = Cache.GetDefaultCacheItemPolicy(dependentKeys, true);
缓存是我自己的对象。 GetDefaultCacheItemPolicy方法返回System.Runtime.Caching.CacheItemPolicy对象。然而,生成的IL看起来像这样:
Func<Feature, FeatureModel> policy = (Func<Feature, FeatureModel>) 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。
万岁!
答案 2 :(得分:2)
留给子孙后代,这不是问题。
查看我的new answer。
这就是我所相信的。
与你所说的相反,我认为该程序实际上并没有在发布的任何行中崩溃,而是在它们之后的一行上崩溃,而你没有发布。
我相信这是因为我也相信你正在进行Release-build,在这种情况下,两个Debug行都将被删除,因为它们被标记为[Conditional("DEBUG")]
属性。
这里的线索是all
变量已经被优化掉了,这应该只在Release-build期间发生,而不是Debug-build。
换句话说,我认为all
变量毕竟是null
,并且Debug行没有被执行,因为它们没有被编译到程序集中。调试器尽职地报告all
变量不再存在。
请注意,所有这一切都应该很容易测试。只需在您发布的两个Debug行中的第一个上放置一个断点即可。如果断点被击中,我的假设很可能是错误的。如果没有(并且我将猜测断点符号在运行时显示为空心圆),那么这些行不会被编译到程序集中。
答案 3 :(得分:0)
如果您怀疑该变量是以某种方式进行了优化,则可以使用IsAlive
对WeakReference
对象的all
属性或通过{{1}进行验证}。我不确定这是否有用,但值得一试。
另一种可能性,虽然极不可能,但由于某种原因,GC.KeepAlive(all)
会抛出异常。您可以使用.NET Reflector等工具验证这一点,以查看该方法的确切功能。