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

时间:2010-07-31 21:44:39

标签: c# nullreferenceexception

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

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

4 个答案:

答案 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)

如果您怀疑该变量是以某种方式进行了优化,则可以使用IsAliveWeakReference对象的all属性或通过{{1}进行验证}。我不确定这是否有用,但值得一试。

另一种可能性,虽然极不可能,但由于某种原因,GC.KeepAlive(all)会抛出异常。您可以使用.NET Reflector等工具验证这一点,以查看该方法的确切功能。