c# code seems to get optimized in an invalid way such that an object value becomes null

后端 未结 3 772
萌比男神i
萌比男神i 2021-02-05 13:13

I have the following code that exhibits a strange problem:

var all = new FeatureService().FindAll();
System.Diagnostics.Debug.Assert(all != null, \"FindAll must          


        
3条回答
  •  既然无缘
    2021-02-05 13:52

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

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

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

    value = new List();
    return value;
    

    here's what Reflector says:

    value = new List();
    bool CS$1$0000 = (bool) value;
    return (List) 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 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 
    }
    

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

提交回复
热议问题