C# - 测试期间出现奇怪的空引用异常,为什么会发生这种情况?

Joh*_*ust 4 c# if-statement nullreferenceexception visual-studio

这引用了我的最后一个问题,该问题似乎已被放弃。如果您使用 C# 和 MS VS 2015,我遇到了一个奇怪的“错误”。要重现该错误,请按照以下步骤操作:

  1. 打开控制台应用程序项目并复制粘贴下面的代码。
  2. 在这里设置断点: 在此输入图像描述
  3. 首先运行代码超过断点,它有效!:D
  4. 然后再次运行代码,但这次在断点处停止并将执行语句光标从此处拖动到 if 语句中: 在此输入图像描述 到这里: 在此输入图像描述

命中Continue并抛出 NRE 异常。为什么会出现这种情况?只有我吗?对此有何技术解释?

代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace testapp
{
    class Program
    {
        static void Main(string[] args)
        {
            FILECollection randomCollection = new FILECollection();
            // Fill with junk test data:
            for(int i = 0; i<10; i++)
            {
                FILE junkfile = new FILE() { fileName = i.ToString(), folderName = i.ToString(), fileHashDigest = new byte[1] };
                randomCollection.Add(junkfile);
            }

            if (true)
            {
                Console.WriteLine("testing this weird exception issue...");
                FILE test;
                test = new FILE();
                test.fileName = "3";
                test.folderName = "3";
                test.fileHashDigest = new byte[1];

                FILE exists = randomCollection.Where(f => f.fileName == test.fileName &&
                                              f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First();
            }
        }
    }


    public class FILE
    {
        public FILE() { _fileName = "";}
        private string _fileName;
        public string fileName
        {

            get
            {
                    if (false)
                        return this._fileName.ToUpper();
                    else
                        return this._fileName;
            }
            set
            {

                    if (false)
                        this._fileName = value.ToUpper();
                    else
                        this._fileName = value;
            }
        }
        public string folderName { get; set; }
        public byte[] fileHashDigest { get; set; }
    }

    public class FILECollection : IEnumerable<FILE>, ICollection<FILE>
    {
        private HashSet<FILE> svgHash;
        private static List<FILE> PreallocationList;
        public string FileName = "N/A";

        /// <summary>
        /// Default Constructor, will not 
        /// preallocate memory.
        /// </summary>
        /// <param name="PreallocationSize"></param>
        public FILECollection()
        {
            this.svgHash = new HashSet<FILE>();
            this.svgHash.Clear();
        }

        /// <summary>
        /// Overload Constructor Preallocates
        /// memory to be used for the new 
        /// FILE Collection.
        /// </summary>
        public FILECollection(int PreallocationSize, string fileName = "N/A", int fileHashDigestSize = 32)
        {
            FileName = fileName;
            PreallocationList = new List<FILE>(PreallocationSize);
            for (int i = 0; i <= PreallocationSize; i++)
            {
                byte[] buffer = new byte[fileHashDigestSize];
                FILE preallocationSVG = new FILE()
                {
                    fileName = "",
                    folderName = "",
                    fileHashDigest = buffer
                };
                PreallocationList.Add(preallocationSVG);
            }
            this.svgHash = new HashSet<FILE>(PreallocationList);
            this.svgHash.Clear(); // Capacity remains unchanged until a call to TrimExcess is made.
        }

        /// <summary>
        /// Add an FILE file to 
        /// the FILE Collection.
        /// </summary>
        /// <param name="svg"></param>
        public void Add(FILE svg)
        {
            this.svgHash.Add(svg);
        }

        /// <summary>
        /// Removes all elements 
        /// from the FILE Collection
        /// </summary>
        public void Clear()
        {
            svgHash.Clear();
        }


        /// <summary>
        /// Determine if the FILE collection
        /// contains the EXACT FILE file, folder, 
        /// and byte[] sequence. This guarantees 
        /// that the collection contains the EXACT
        /// file you are looking for.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(FILE item)
        {
            return svgHash.Any(f => f.fileHashDigest.SequenceEqual(item.fileHashDigest) &&
                                    f.fileName == item.fileName &&
                                    f.folderName == item.folderName);
        }

        /// <summary>
        /// Determine if the FILE collection 
        /// contains the same file and folder name, 
        /// byte[] sequence is not compared. The file and folder
        /// name may be the same but this does not guarantee the 
        /// file contents are exactly the same. Use Contains() instead.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool ContainsPartially(FILE item)
        {
            return svgHash.Any(f => f.fileName == item.fileName &&
                                    f.folderName == item.folderName);
        }

        /// <summary>
        /// Returns the total number
        /// of FILE files in the Collection.
        /// </summary>
        public int Count
        { get { return svgHash.Count(); } }

        public bool IsReadOnly
        { get { return true; } }

        public void CopyTo(FILE[] array, int arrayIndex)
        {
            svgHash.CopyTo(array, arrayIndex);
        }

        public bool Remove(FILE item)
        {
            return svgHash.Remove(item);
        }

        public IEnumerator<FILE> GetEnumerator()
        {
            return svgHash.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return svgHash.GetEnumerator();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为要么我以一种非常错误的方式进行调试,要么微软应该看看这个。这就像未来的代码正在破坏当前的代码......这是不可能的!

在此输入图像描述

KMo*_*ssa 5

好吧,这是我最好的猜测..

\n\n

首先,正如我在评论中提到的,如果您注释掉该行,则不会发生异常FILE exists = randomCollection.Where(f => f.fileName == test.fileName && f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First()\xe2\x80\x8c\xe2\x80\x8b;

\n\n

其次,我注意到可以使用以下代码重现相同的行为:

\n\n
if (true)\n{\n    object o;\n    o = new object();\n    Func<bool> m = () => o == null;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

即原因似乎与 lambda 表达式中使用的变量有关。因此,在 ILSpy 中查看上面的相同代码片段,我得到以下结果:

\n\n
Program.<>c__DisplayClass0_0 <>c__DisplayClass0_ = new Program.<>c__DisplayClass0_0();\n<>c__DisplayClass0_.o = new object();\nFunc<bool> func = new Func<bool>(<>c__DisplayClass0_.<Main>b__0);\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以我最好的猜测是 指NullReferenceException的是<>c__DisplayClass0_实例null- 因此我倾向于相信逐步执行实际上跳过了实例化的if(true)第一行<>c__DisplayClass0_

\n

  • 我将此问题发送给了一位前 Microsoft 开发人员,他实际上领导了 C# 编译器的开发团队。这是他的回答:“当您在调试器中动态移动执行点时,无法保证您的程序继续按预期工作。编译器生成的代码假定该代码将正常运行。如果你违反了这个假设,那么糟糕的事情就会发生;很高兴发生的最糟糕的事情是空引用异常。 (2认同)