Vag*_*lov 4 stack-overflow f# overriding 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()
Run Code Online (Sandbox Code Playgroud)
DiscrUnion.ToString()的打印方式类似于"FSI_0003 + DiscrUnion",但是对于DiscrUnionWithToString.ToString(),我得到了实际的属性:Text"text".
到现在为止还挺好.但是,对于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()
Run Code Online (Sandbox Code Playgroud)
甚至不要尝试实例化PocoTypeWithToString.StackOverflowException.
我理解,对于POCO类型,尝试使用"%A"格式化指令会导致ToString调用,因此当ToString本身包含此类指令时,它将失败.但ToString覆盖的正确方法是什么?我应该只注意C#类型(有区别的工会和记录似乎工作正常),还是有其他事情需要注意?
发生StackOverflowException的原因是打印机使用GetValueInfoOfObject格式化.如您所见,如果对象是F#对象,则它具有如何处理它们的特殊情况(元组,函数,联合,异常,记录).
但是,如果它不是这些情况之一,它将成为一个ObjectValue(obj).稍后,在reprL我们有一些特殊情况下处理ObjectValue诸如字符串,数组,映射/集合,ienumerable之类的s,然后在最后如果失败,它将使它成为类型的基本布局(let basicL = LayoutOps.objL obj)Leaf.
很久以后,Leaf使用格式化leafformatter.leafformatter可以处理原语,但是当它处理一个复杂的对象(如POCO)时,它会let text = obj.ToString()导致无限循环和StackOverflow异常.
解决方案是不要%A在POCO上使用.
好消息是,F#的下一个版本可能ToString有效地实现了记录/联合的默认实现override this.ToString() = sprintf "%A" this.它的实现部分完成:https://github.com/Microsoft/visualfsharp/pull/1589.它可以解决您必须开始的问题.
| 归档时间: |
|
| 查看次数: |
228 次 |
| 最近记录: |