C# 代码似乎以无效的方式进行优化,导致对象值变为 null

发布于 2024-09-12 11:14:15 字数 2266 浏览 2 评论 0原文

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

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”变量不为空,但在下一行中它似乎为空。

该问题并非特定于调用 ToString() 方法时失败。我将其简化为这个可重现的示例,同时尝试追踪根本原因。

这可能是一个线索:在调试器中,变量“all”出现在“本地”窗口中,其值为“无法获取本地或参数‘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 异常。我发布的代码是在跟踪问题一段时间后得出的。

更新#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

I have the following code that exhibits a strange problem:

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

The signature of the FindAll method is:

public List<FeatureModel> FindAll()

Stepping through the code I have confirmed that the return value from FindAll is not null, and as you can see from the Assert, the "all" variable is not null, yet in the following line it appears to be null.

The issue is not specific to failing when the ToString() method is called. I simplified it down to this reproducible example while trying to trace the root cause.

This may be a clue: in the debugger, the variable "all" appears in the Locals window with a value of "Cannot obtain value of local or argument 'all' as it is not available at this instruction pointer, possibly because it has been optimized away."

I considered trying one of the approaches documented elsewhere for disabling code optimization but this wouldn't really solve the problem since the release version of the code will still be optimized.

I am using Visual Studio 2010 with .NET 4.0.

Any thoughts?

UPDATE: per request, here is the entire method:

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;
}

As an FYI, the original implementation was simply:

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

I originally encountered the null exception in the calling method. The code I posted was after tracing the issue for a while.

UPDATE #2: As requested, here is the stack trace from the 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

左秋 2024-09-19 11:14:15

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


在设法将问题减少到几个简单的项目和文件之后,我发布了一个要求验证的问题。此处提供:可能的 C# 4.0 编译器错误,其他人可以验证吗?


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

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 中的调用从 this: 更改

var all = fs.FindAll();

为 this:

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

那么程序就会崩溃并出现 ExecutionEngineException


完成清理、构建,然后通过 Reflector 查看编译后的代码后,输出如下所示(滚动到代码底部以了解重要部分):

public List<FeatureModel> FindAll()
{
    List<FeatureModel> value;
    Func<FeatureModel, string> CS
lt;>9__CachedAnonymousMethodDelegate6 = null;
    List<FeatureModel> CS
lt;>9__CachedAnonymousMethodDelegate7 = null;
    string key = base.Cache.GetQueryKey("FindAll");
    if (base.Cache.Load<List<FeatureModel>>(key) == null)
    {
        if (CS
lt;>9__CachedAnonymousMethodDelegate6 == null)
        {
            CS
lt;>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
lt;>9__CachedAnonymousMethodDelegate6)).ToList<FeatureModel>();
        if (CS
lt;>9__CachedAnonymousMethodDelegate7 == null)
        {
            CS
lt;>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
lt;>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
lt;>9__CachedAnonymousMethodDelegate6,
        [5] class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> CS
lt;>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 
}

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

After looking over the code over TeamViewer, and finally downloading, compiling and running the code on my own machine, I believe this is a case of compiler error in C# 4.0.


I've posted a question with request for verification, after managing to reduce the problem to a few simple projects and files. It is available here: Possible C# 4.0 compiler error, can others verify?


The likely culprit is not this method:

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;
}

But the method it calls, 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;
}

If I changed the call in GetModels from this:

var all = fs.FindAll();

to this:

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

then the program crashes with a ExecutionEngineException.


After doing a clean, a build, and then looking at the compiled code through Reflector, here's how the output looks (scroll to the bottom of the code for the important part):

public List<FeatureModel> FindAll()
{
    List<FeatureModel> value;
    Func<FeatureModel, string> CS
lt;>9__CachedAnonymousMethodDelegate6 = null;
    List<FeatureModel> CS
lt;>9__CachedAnonymousMethodDelegate7 = null;
    string key = base.Cache.GetQueryKey("FindAll");
    if (base.Cache.Load<List<FeatureModel>>(key) == null)
    {
        if (CS
lt;>9__CachedAnonymousMethodDelegate6 == null)
        {
            CS
lt;>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
lt;>9__CachedAnonymousMethodDelegate6)).ToList<FeatureModel>();
        if (CS
lt;>9__CachedAnonymousMethodDelegate7 == null)
        {
            CS
lt;>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
lt;>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;
}

Notice the 3 last lines of the method, here's what they look like in the code:

value = new List<FeatureModel>();
return value;

here's what Reflector says:

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

It creates the list, then casts it to a boolean, then casts it back to a list and returns it. Most likely this causes a stack problem.

Here's the same method, in IL (still through Reflector), I've stripped away most of the code:

.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
lt;>9__CachedAnonymousMethodDelegate6,
        [5] class [mscorlib]System.Collections.Generic.List`1<class FeatureCrowd.DomainModel.FeatureModel> CS
lt;>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 
}

Here's a screencast showing the debug session, if you just want the Reflector output, skip to about 2:50.

我一向站在原地 2024-09-19 11:14:15

在 Lasse 发现 FindAll 方法生成错误的 IL 后,我发现了另一个也生成错误 IL 的方法 - 我还找到了根本原因和解决方案。

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

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

Cache 是我自己的对象。 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。

万岁!

After Lasse discovered that the FindAll method was generating the wrong IL, I then came across another method that was also generating the wrong IL -- I also found the root cause and resolution.

The relevant line in the second method is:

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

Cache is my own object. The GetDefaultCacheItemPolicy method returns a System.Runtime.Caching.CacheItemPolicy object. The generated IL, however, looked like this:

Func<Feature, FeatureModel> policy = (Func<Feature, FeatureModel>) base.Cache.GetDefaultCacheItemPolicy(dependentKeys, true);

There are two projects in play here. The methods that are generating the wrong IL are in one project called DomainModel, and the Cache object is in a Utilities project, which is referenced by the first. The second project contains a reference to System.Runtime.Caching but the first does not.

The fix was to add a reference to System.Runtime.Caching to the first project. Now the generated IL looks correct:

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

The first method (that Lasse posted about in his answer) now also generates proper IL.

Hooray!

|煩躁 2024-09-19 11:14:15

留给后人,这不是问题。

请参阅我的 新答案


这就是我所相信的。

与您所说的相反,我相信该程序实际上并没有在已发布的任何行中崩溃,而是在您尚未发布的后续行中崩溃。

我相信这一点的原因是我也相信您正在执行发布构建,在这种情况下,两个调试行都将被删除,因为它们被标记为 [Conditional("DEBUG")] 属性。

这里的线索是 all 变量已被优化掉,这应该只发生在发布构建期间,而不是调试构建期间。

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

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

Left for posterity, this is not the problem.

See my new answer.


Here's what I believe.

Contrary to what you're saying, I believe the program is not in fact crashing in any of the lines posted, but instead crashes on one of the lines following them, which you haven't posted.

The reason I believe this is that I also believe you're doing a Release-build, in which case both Debug lines will be removed, since they're tagged with a [Conditional("DEBUG")] attribute.

The clue here is that the all variable has been optimized away, and this should only happen during a Release-build, not a Debug-build.

In other words, I believe the all variable is actuall null after all, and the Debug lines aren't executed, because they're not compiled into the assembly. The debugger is dutifully reporting that the all variable no longer exists.

Note that all of this should be easy to test. Just place a breakpoint on the first of the two Debug-lines that you've posted. If the breakpoint is hit, my hypothesis is most likely wrong. If it doesn't (and I'm going to guess that the breakpoint symbol shows up as a hollow circle at runtime), then those lines aren't compiled into the assembly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文