2016-10-20 3 views
3

F # имеет директиву форматирования «% A», которая очень эффективна, поскольку вызывает форматирование для расширения типов и перечисления отдельных членов. В некоторых местах в наших приложениях данные регистрируются с использованием метода ToString (для этого есть некоторые технические причины), а затем для типов, таких как дискриминационные объединения, это только имя типа, которое регистрируется. Слишком плохо, поэтому мы начали переопределять методы ToString для некоторых типов.Избегание переполнения стека при переопределении 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 попытка использовать директиву форматирования «% А» вызывает вызов ToString, поэтому, когда ToString сама содержит такую ​​директиву, она потерпит неудачу. Но как правильно переопределить ToString? И должен ли я остерегаться только типов типов C# (дискриминационные союзы и записи, похоже, работают нормально), или есть другие вещи, о которых нужно знать?

ответ

5

Причина, по которой происходит StackOverflowException, заключается в том, что принтер использует GetValueInfoOfObject для форматирования. Как вы можете видеть, если объект является объектом F #, он имеет особые случаи для того, как справляться с ними (кортежи, функции, союзы, исключения, записи).

Однако, если это не один из этих случаев, он сделает его ObjectValue(obj). Позже, в reprL, у нас есть некоторые особые случаи, связанные с ObjectValue s, такие как строка, массив, карта/набор, ienumerable, а затем в конце, если это не удается, оно просто сделает основной макет (let basicL = LayoutOps.objL obj) типа Leaf ,

Много позже, что Leaf отформатирован с использованием leafformatter. leafformatter может обрабатывать примитивы, но когда речь идет о сложном объекте, таком как ваш POCO, он делает let text = obj.ToString(), что приводит к бесконечному циклу и исключению StackOverflow.

Решение не должно использовать %A на POCOs.

Хорошая новость заключается в том, что следующая версия F # может иметь реализацию по умолчанию ToString для записей/союзов, которые эффективно override this.ToString() = sprintf "%A" this. Реализация для этого частично завершена здесь: https://github.com/Microsoft/visualfsharp/pull/1589. Это может решить проблему, с которой вы должны были начать.

+0

Большое спасибо! Полезно знать, что ToString может быть улучшено, чтобы быть более полезным для родных типов F #. –

3

Простой ответ - не используйте такую ​​оболочку для ToString всюду.

Формат строки %A запускает довольно волосатый принтер на основе отражения, который может вернуться к ToString для случаев, когда он не обрабатывается особым образом. См. Код для anyToStringForPrintfhere.

Чистое решение должно состоять в том, чтобы иметь один sprintf %A в точке, где вы регистрируете объекты, а не иметь все инструменты DU с шаблоном ToString, но вы говорите, что это не вариант.

Для обычного класса .NET (в отличие от записи или объединения F #) не используйте this - вместо этого используйте какой-либо значащий идентификатор или выведите все элементы или сделайте все, что вам нравится. Просто не начинайте бесконечную петлю ToStrings.

+0

Благодарим вас за ссылку на anyToStringForPrintf - объясняет довольно много. И ваш совет прост и практичен - я просто не должен ожидать легкого выхода из решения любого типа с помощью одного вызова. –

Смежные вопросы