jonsequitur / dotnet-repl

A polyglot REPL built on .NET Interactive

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Stackoverflow on circular referenced records.

WhiteBlackGoose opened this issue · comments

First, awesome project!

Second... yes, it's the first thing I tried in it. Here's the repro:

record A(int P1) { public A Node { get; set; } }
var a = new A(5);
a.Node = a;
a

I suspect it's a problem of dotnet/interactive. Should I redirect the issue there?

This is actually a bug here. .NET Interactive will handle this correctly because the default formatters will check for recursion correctly. dotnet-repl has different formatter implementations targeting Spectre.Console.

This is fixed in version 0.1.30.

This is actually not fixed for records.

The underlying issue here is one with ToString when records have reference cycles. See: dotnet/roslyn-analyzers#5068

@jonsequitur it's more than that. It's not ToString which causes it. Maybe you're calling PrintMembers? Otherwise, here's my output for my lib's records:
image

The formatters do recursive evaluation but they also have built-in recursion controls, e.g.:

image

Does this repro on the latest version and does it repro in a .NET Interactive notebook?

How can I repro it?

Updated the tool to 1.44 version.
image

You are using classes, and I'm using record. In record it is by default overriden to printing its members recurisively => circular reference implies an SO.

But if I add my own overriding,

record A(int P1) { public A Node { get; set; } public override string ToString() => "Quack!"; }
var a = new A(5);
a.Node = a;
a

it results in a similar output you have
image

Now, if I override PrintMembres too

sealed record A(int P1) { public A Node { get; set; } public override string ToString() => "Quack!";  bool PrintMembers(System.Text.StringBuilder sb) => true; }
var a = new A(5);
a.Node = a;
a.ToString()

image

But if I don't do ToString in the end, I get

image

My suggestion is to simply call ToString on a record. Yes, you would get an SO if the author of the record didn't override the necessary methods, but at least it would coincide with the expectations.

Here's a non-recursive example comparing the formatter to the ToString output:

image

The formatter output is definitely a little redundant.

Let's look at the larger goal of using formatters instead of ToString. We wanted to provide something richer than whatever the original developer decided to use ToString for. I agree that records have a more useful default ToString implementation than classes, so maybe a different behavior is warranted in this case. The general expectation in interactive though (analogous for example to F# printers) is that ToString is typically not sufficient.

Take recursive formatting. If you want to format a record with more complex members and those records are of types where you also have custom formatting rules, those will be in effect with the formatter approach:

image