F#有一个非常强大的格式化指令“%A”,因为触发格式化程序来扩展类型并列出单个成员。在我们的应用程序的某些地方,使用ToString方法记录数据(有一些技术原因),然后对于有区别的联合类型,它只是一个记录的类型名称。太糟糕了,所以我们开始为某些类型重写ToString方法。
举个例子:
open System
type DiscrUnion =
| Text of string
let t1 = DiscrUnion.Text "text"
sprintf "%A" t1
sprintf "%s" <| t1.ToString()
type DiscrUnionWithToString =
| Text of string
override this.ToString() = sprintf "%A" this
let t2 = DiscrUnionWithToString.Text "text"
sprintf "%A" t2
sprintf "%s" <| t2.ToString()
DiscrUnion.ToString()的打印方式类似于“FSI_0003 + DiscrUnion”,但对于DiscrUnionWithToString.ToString()我得到了实际的属性:文本“text”。< / p>
到目前为止一切顺利。但是,对于CLR类型,这种覆盖会导致灾难性的结果:堆栈溢出!这是一个例子:
type PocoType() =
member val Text : string = null with get, set
let t3 = PocoType()
t3.Text <- "text"
sprintf "%A" t3
sprintf "%s" <| t3.ToString()
type PocoTypeWithToString() =
member val Text : string = null with get, set
override this.ToString() = sprintf "%A" this
let t4 = PocoTypeWithToString()
t4.Text <- "text"
sprintf "%A" t4
sprintf "%s" <| t4.ToString()
甚至不尝试实例化PocoTypeWithToString。 StackOverflowException。
据我所知,对于POCO类型,尝试使用“%A”格式化指令会导致ToString调用,因此当ToString本身包含此类指令时,它将失败。但ToString覆盖的正确方法是什么?我应该只注意C#类型(有区别的工会和记录似乎工作正常),还是有其他事情需要注意?
答案 0 :(得分:5)
发生StackOverflowException的原因是打印机使用GetValueInfoOfObject
进行格式化。如您所见,如果对象是F#对象,则它具有如何处理它们的特殊情况(元组,函数,联合,异常,记录)。
但是,如果不是其中一种情况,则会使其成为ObjectValue(obj)
。稍后,在reprL
中我们有一些特殊情况来处理ObjectValue
,例如字符串,数组,映射/集合,ienumerable,然后在最后如果失败,它只会使它成为一个类型let basicL = LayoutOps.objL obj
的基本布局(Leaf
)。
很久以后,使用Leaf
格式化leafformatter
。 leafformatter
可以处理原语,但是当它处理复杂对象(如POCO)时,它会let text = obj.ToString()
,这会导致无限循环和StackOverflow异常。
解决方案是不在POCO上使用%A
。
好消息是F#的下一个版本可能对有效ToString
的记录/联合实施默认override this.ToString() = sprintf "%A" this
实施。它的实现部分完成:https://github.com/Microsoft/visualfsharp/pull/1589。它可以解决您必须开始的问题。
答案 1 :(得分:3)
简单回答 - 不要在任何地方使用ToString
这样的一揽子实施。
格式字符串%A
启动一个相当多毛的基于反射的打印机,对于它不能以特殊方式处理的情况,它可能会回退到ToString
。请参阅anyToStringForPrintf
here的代码。
更清洁的解决方案是在您记录对象时使用单个sprintf %A
,而不是让所有DU实现样板ToString
,但您说这不是一个选项。< / p>
对于常规.NET类(而不是F#特定记录或联合),不要使用this
- 而是使用一些有意义的标识符或输出所有成员,或者做任何你喜欢的事情。只是不要开始ToStrings
的无限循环。
答案 2 :(得分:0)
DiscrUnionWithToString.ToString()我得到了实际的属性:文本“文本”
当我遇到这个问题时,我想到了这个
type DiscrUnionWithToString =
| Text of string
override text.ToString() =
match text with
| Text text -> text