4

This references my last question which appears to have been abandoned. I am experiencing an odd "bug" if you will with C# and MS VS 2015. To reproduce the error, follow the steps:

  1. Open console app project and copy paste code below.
  2. Set a break point here: enter image description here
  3. First run code past break point, it works! :D
  4. Then run code again but this time STOP at the break point and DRAG the executing statement cursor INTO the if statement from here: enter image description here to here: enter image description here

Hit Continue and an NRE exception is thrown. Why does this happen? Is it just me? What is the technical explination for this?

CODE:

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

I think either I am debugging in a terribly wrong way, or Microsoft should take a look at this. It's like future code is breaking current code...which is impossible!

enter image description here

15
  • Possible duplicate of What is a NullReferenceException, and how do I fix it? Commented Oct 11, 2016 at 18:30
  • 1
    You guys are missing the point, I get that it's a NRE, but it ONLY gets thrown when you use the debugger. This is not a code issue (that I know of). Commented Oct 11, 2016 at 18:31
  • 2
    What's the problem? I would expect a Null Reference Exception for test in the watches because it hasn't been instantiated yet. Put your breakpoint after the test = new FILE() line Commented Oct 11, 2016 at 18:37
  • Is the null exception thrown from the code or are you just seeing it in the watch window when evaluating the "test" object? Commented Oct 11, 2016 at 18:39
  • 1
    Are you sure you target in "debug "mode? If you target in "release", the code is optimized and the breakpoint has no correlation with actual code. Commented Oct 11, 2016 at 18:45

1 Answer 1

5

OK here's my best guess..

First, as I mentioned in the comments, the exception doesn't occur if you comment out the line FILE exists = randomCollection.Where(f => f.fileName == test.fileName && f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First()‌​;

Second, I noticed the same behavior can be reproduced with the following code:

if (true) { object o; o = new object(); Func<bool> m = () => o == null; } 

i.e. the cause seems to be related to the variable being used in a lambda expression. So, looking at the same code snippet above in ILSpy I get the following:

Program.<>c__DisplayClass0_0 <>c__DisplayClass0_ = new Program.<>c__DisplayClass0_0(); <>c__DisplayClass0_.o = new object(); Func<bool> func = new Func<bool>(<>c__DisplayClass0_.<Main>b__0); 

so my best guess is that the NullReferenceException refers to <>c__DisplayClass0_ intance being null - and I'm therefore inclined to believe that the stepping through the if(true) actually skipped the first line where <>c__DisplayClass0_ is instantiated

Sign up to request clarification or add additional context in comments.

4 Comments

After some debugging with proper IL offsets, I totally agree with your conclusion. Because there is no nop between L_0003: stloc.s flag and L_0005: newobj instance void testapp.Program/<>c__DisplayClass1::.ctor() to which Visual Studio can jump to in debug mode, it just skips the creation of the <>c__DisplayClass1. The next available L_000b: nop is then just before the new object() line. (The nops are a place in debug mode where VS can set breakpoints or jump to if you drag the current statement.) This behavior is very interesting and feels a bit like a bug in the compiler...
I sent this issue to a former Microsoft developer who literally lead the development team for the C# compiler. This was his response: When you move the point of execution around dynamically in the debugger, there is no guarantee that your program continues to work as expected. The compiler generates code that assumes that the code is going to be run normally. If you violate that assumption then bad things can happen; be very happy that the worst thing that happened was a null reference exception.
continued: The compiler is not required to generate code that is robust in the face of you moving around the point of control at your whim, and the debugger is not required to do more than a good-faith effort to move the point of control around, assuming that you know what you are doing and what the consequences will be.
So basically, what is happening here is that when the compiler generated code, it assumed I would not be pulling the point of control into the body of the if statement. @KMoussa is almost certainly correct in saying that initialization is skipped.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.