-
Notifications
You must be signed in to change notification settings - Fork 842
Description
The following code displays inconsistent behavior for how different types are formatted when printing:
[<StructuredFormatDisplayAttribute("Custom structured format")>]
type MyRecord = {
a: int
} with
override _.ToString() = "Custom ToString()"
interface System.IFormattable with
member _.ToString (_format, _provider) = "Custom IFormattable"
type OtherRecord = {
b: MyRecord
}
type MyUnion = | MyUnion of MyRecord
[<EntryPoint>]
let main argv =
let myRec = {
a = 10
}
let otherRec = {
b = myRec
}
let myUnion = MyUnion myRec
printfn $"{myRec}"
printfn $"{otherRec}"
printfn $"{myUnion}"
let myTuple = (myRec, myRec)
printfn $"{myTuple}"
0
Expected behavior
Ideally The above code would print
Custom IFormattable
{ b = { a = Custom IFormattable } }
MyUnion { a = Custom IFormattable }
(Custom IFormattable, Custom IFormattable)
OR
Custom ToString()
{ b = { a = Custom ToString() } }
MyUnion { a = Custom ToString() }
(Custom ToString(), Custom ToString())
OR
Custom structured format
{ b = Custom structured format }
MyUnion Custom structured format
(Custom structured format,Custom structured format)
In order for the formatting behavior of different types to be consistent.
Actual behavior
The above code prints
Custom IFormattable
{ b = Custom structured format }
MyUnion Custom structured format
(Custom ToString(), Custom ToString())
So it seems that direct string interpolation calls the System.IFormattable.ToString function. Tuple types on the other hand call
ToString() on each of their components. Record types and discriminated union types use the StructuredFormatDisplayAttribute. This behavior is inconsistent and results in a headache when you want to define a custom way for your record type to be printed, and want to have it "just work" regardless of the context in which it is being printed.
Known workarounds
Doing this
[<StructuredFormatDisplayAttribute("Custom structured format")>]
type MyRecord = {
a: int
} with
override _.ToString() = "Custom ToString()"
interface System.IFormattable with
member this.ToString (_format, _provider) = this.ToString ()
Will bring the direct string interpolation and tuple printing in line with each other, but does not solve the issue for nested record types and discriminated unions. Passing something like "{ToString()}" to the StructuredFormatDisplayAttribute does not work. At runtime it results in <StructuredFormatDisplay exception: Method 'Program+MyRecord.this.ToString()' not found.>.
I cannot find any way to solve the problem I am trying to solve, which is that any time an instance of MyRecord is printed, it should always result in the exact same string, regardless of whether it is printed directly, or printed because it's a member of another record that's being printed, or printed because it's a member of a record that's inside a tuple that's inside another record that's inside of a DU case that's inside of a tuple that's....etc.
Related information
- Operating system: Windows 10
- .NET Runtime kind: .NET 5.0
- F# 6.0.1
- Visual Studio 2019 16.11.9