2016-10-20 11 views
3

F#は書式設定ディレクティブ "%A"を持っています。これは、型を拡張して個々のメンバーをリストするフォーマッタをトリガするので非常に強力です。私たちのアプリケーションのいくつかの場所では、ToStringメソッドを使用してデータを記録しています(それにはいくつかの技術的な理由があります)。そして、区別された共用体のような型の場合は、ログに記録される型名だけです。あまりにも悪いので、いくつかの型のToStringメソッドをオーバーライドし始めました。F#のToStringをオーバーライドするときのスタックオーバーフローの回避F#

はあなたの例を与えるために:

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()のために、私は実際のプロパティを取得:テキスト「テキスト"

これまでのところとても良いです。ただし、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#の種類の型(差別化された共用体とレコードは正常に動作しているようです)、または注意すべきことがありますか?

答えて

5

StackOverflowExceptionが発生する理由は、プリンタがGetValueInfoOfObjectを使用して書式を設定するためです。あなたが見ることができるように、オブジェクトがF#オブジェクトであれば、それらを扱う特別なケース(タプル、関数、共用体、例外、レコード)があります。

ただし、そうでない場合はObjectValue(obj)となります。後でreprLObjectValueの文字列、配列、map/set、ienumerableなどの特殊ケースがあり、それが失敗した場合は、Leafの基本レイアウト(let basicL = LayoutOps.objL obj)になります。 。

多くの場合、Leafleafformatterを使用してフォーマットされます。 leafformatterはプリミティブを扱うことができますが、POCOなどの複雑なオブジェクトを処理する場合はlet text = obj.ToString()となり、ループが無限になり、StackOverflow例外が発生します。

解決策は、POCOで%Aを使用しないことです。

良いニュースは、F#次のバージョンがが効果的であるoverride this.ToString() = sprintf "%A" this記録/労働組合のデフォルトToString実装を持っているかもしれないということです。その実装はここで部分的に完了しています:https://github.com/Microsoft/visualfsharp/pull/1589。あなたが最初にやらなければならなかった問題を解決するかもしれません。

+0

ありがとうございました! ToStringがF#のネイティブ型の方がより便利になるように改善されていることを知っておきましょう。 –

3

簡単な答え - どこでもToStringのようなブランケットの実装を使用しないでください。

フォーマット文字列%A特別な方法で処理しない場合は、ToStringに戻ってしまうかもしれないかなり毛深い反射ベースのプリンタを起動します。 anyToStringForPrintfhereのコードを参照してください。

すべてのDUがボイラープレートToStringを実装するのではなく、オブジェクトをログに記録する時点で、クリーナーソリューションではsprintf %Aと表示されますが、これはオプションではないと言います。

通常の.NETクラス(F#固有のレコードまたは共用体ではありません)の場合は、thisを使用しないでください。代わりに意味のある識別子を使用するか、すべてのメンバーを出力するか、好きなことをしてください。ただToStringsの無限ループを開始しないでください。

+0

anyToStringForPrintfへのリンクありがとう - 非常に多く説明しています。あなたのアドバイスはシンプルで実用的です。一回の呼び出しであらゆるタイプの問題を解決する簡単な方法は期待してはいけません。 –