在F#中覆盖ToString时避免堆栈溢出

时间:2016-10-20 14:45:42

标签: f# override stack-overflow tostring

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#类型(有区别的工会和记录似乎工作正常),还是有其他事情需要注意?

3 个答案:

答案 0 :(得分:5)

发生StackOverflowException的原因是打印机使用GetValueInfoOfObject进行格式化。如您所见,如果对象是F#对象,则它具有如何处理它们的特殊情况(元组,函数,联合,异常,记录)。

但是,如果不是其中一种情况,则会使其成为ObjectValue(obj)。稍后,在reprL中我们有一些特殊情况来处理ObjectValue,例如字符串,数组,映射/集合,ienumerable,然后在最后如果失败,它只会使它成为一个类型let basicL = LayoutOps.objL obj的基本布局(Leaf)。

很久以后,使用Leaf格式化leafformatterleafformatter可以处理原语,但是当它处理复杂对象(如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