C# record ToString() causes stack overflow and stops debugging session with a strange error

Viewed 156

I wrote a unit test and used the new C# record to store some data I needed for testing. The unit test run fine, but when I set a break point and moved the mouse over the record variable name, the debugging session ended and I got a strange looking error message.

To report the problem to Microsoft, I wrote a simple unit test, which demonstrates the problem, but doesn't make much sense otherwise:

#nullable enable
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CSharpRecordTest {

  record Twin(string Name) {
    public Twin? OtherTwin { get; set; }
  }

  [TestClass]
  public class UnitTest1 {
    [TestMethod]
    public void TestMethod1() {
      var twinA = new Twin("A");
      var twinB = new Twin("B");
      twinA.OtherTwin = twinB;
      twinB.OtherTwin = twinA;
      Assert.AreEqual(twinA, twinA.OtherTwin.OtherTwin);
    }
  }
}

The test runs fine, but when setting a break point at Assert.AreEqual( and moving the mouse over twinA, the debugging session stops and this error message gets shown:

enter image description here

1 Answers

It took me a day or two to figure out what was happening, because the debugger did not let me see anything. I wrote a console application:

class Program {
  record Twin(string Name) {
    public Twin? OtherTwin { get; set; }
  }

  static void Main(string[] args) {
    var twinA = new Twin("A");
    var twinB = new Twin("B");
    twinA.OtherTwin = twinB;
    twinB.OtherTwin = twinA;
    Console.WriteLine(twinA);
  }
}

The console application also run into troubles, but at least it showed that there was a stack overrun and which line it caused:

Console.WriteLine(twinA);

The stack looked something like this (although more complicated):

twinA.OtherTwin.OtherTwin.OtherTwin.OtherTwin.OtherTwin.OtherTwin...OtherTwin.ToString()

The problem is that ToString(), which gets automatically written by the compiler, calls .ToString() again on every property.

If a record references itself or other records which then reference the first record, ToString() will fail and needs to get overriden together with Equals(), GetHashCode() and maybe more (I didn't try it).

Actually, when having a property which is not readonly, Equals() and GetHashCode() should be overriden, otherwise classes like Dictionary will not work.

For more details, see my article on CodeProject: C# 9 Record: Compiler Created ToString() Code can Lead to Stack Overflow and Worse

Related