Я пытаюсь рекурсивно распечатать все свойства объектов и свойства подтипа и т.д. Моя объектная модель следующие...
type suggestedFooWidget = {
value: float ;
hasIncreasedSinceLastPeriod: bool ;
}
type firmIdentifier = {
firmId: int ;
firmName: string ;
}
type authorIdentifier = {
authorId: int ;
authorName: string ;
firm: firmIdentifier ;
}
type denormalizedSuggestedFooWidgets = {
id: int ;
ticker: string ;
direction: string ;
author: authorIdentifier ;
totalAbsoluteWidget: suggestedFooWidget ;
totalSectorWidget: suggestedFooWidget ;
totalExchangeWidget: suggestedFooWidget ;
todaysAbsoluteWidget: suggestedFooWidget ;
msdAbsoluteWidget: suggestedFooWidget ;
msdSectorWidget: suggestedFooWidget ;
msdExchangeWidget: suggestedFooWidget ;
}
И моя рекурсия основана на следующем сопоставлении с образцом...
let rec printObj (o : obj) (sb : StringBuilder) (depth : int)
let props = o.GetType().GetProperties()
let enumer = props.GetEnumerator()
while enumer.MoveNext() do
let currObj = (enumer.Current : obj)
ignore <|
match currObj with
| :? string as s -> sb.Append(s.ToString())
| :? bool as c -> sb.Append(c.ToString())
| :? int as i -> sb.Append(i.ToString())
| :? float as i -> sb.Append(i.ToString())
| _ -> printObj currObj sb (depth + 1)
sb
В отладчике я вижу, что currObj имеет строку типа, интервал, плавание, и т.д. но это всегда переходит к defualt случаю внизу. Какая-либо идея, почему это происходит?
Вот как я заставил это работать...
let getMethod = prop.GetGetMethod()
let value = getMethod.Invoke(o, Array.empty)
ignore <|
match value with
| :? float as f -> sb.Append(f.ToString() + ", ") |> ignore
...
В вашем примере enumer.Current
- это объект, содержащий PropertyInfo. Это означает, что currObj всегда является объектом PropertyInfo и всегда будет соответствовать последнему регистру в вашем операторе сопоставления.
Поскольку вас интересует тип значения свойства, вам необходимо вызвать метод GetValue () PropertyInfo, чтобы получить фактическое значение свойства (как в Ответ ChaosPandion).
Поскольку Enumerator возвращает свои значения как объекты, вам также необходимо преобразовать enum.current в PropertyInfo, прежде чем вы сможете получить доступ к GetValue.
Попробуйте заменить
let currObj = (enumer.Current : obj)
на
let currObj = unbox<PropertyInfo>(enumer.Current).GetValue (o, null)
С этим изменением я могу заставить ваш код работать (в FSI):
> let test = {authorId = 42; authorName = "Adams"; firm = {firmId = 1; firmName = "GloboCorp inc."} };;
> string <| printObj test (new StringBuilder()) 1;;
val it : string = "42Adams1GloboCorp inc."
Как уже отмечали другие, вам нужно вызвать член GetValue
, чтобы получить значение свойства - реализованная вами итерация перебирает объекты PropertyInfo
, которые являются "дескрипторами свойства", а не фактическими значениями. Однако я не совсем понимаю, почему вы используете GetEnumerator
и while
цикл в явном виде, когда то же самое можно написать с помощью for
цикла.
Также не нужно игнорировать значение, возвращаемое вызовом sb.Append
- вы можете просто вернуть его как общий результат (потому что это the StringBuilder
). Это фактически сделает код более эффективным (поскольку позволяет оптимизировать хвостовые вызовы). И последнее, вам не нужен ToString
в sb.Append(...)
, потому что метод Append
перегружен и работает для всех стандартных типов.
Таким образом, после некоторых упрощений вы можете получить что-то вроде этого (на самом деле здесь не используется параметр depth
, но я предполагаю, что вы захотите использовать его для чего-то позже):
let rec printObj (o : obj) (sb : StringBuilder) (depth : int) =
let props = o.GetType().GetProperties()
for propInfo in props do
let propValue = propInfo.GetValue(o, null)
match propValue with
| :? string as s -> sb.Append(s)
| :? bool as c -> sb.Append(c)
| :? int as i -> sb.Append(i)
| :? float as i -> sb.Append(i)
| _ -> printObj currObj sb (depth + 1)
Вместо этого вам нужно что-то вроде этого.
let rec printObj (o : obj) (sb : StringBuilder) (depth : int)
let props = o.GetType().GetProperties() :> IEnumerable<PropertyInfo>
let enumer = props.GetEnumerator()
while enumer.MoveNext() do
let currObj = (enumer.Current.GetValue (o, null)) :> obj
ignore <|
match currObj with
| :? string as s -> sb.Append(s.ToString())
| :? bool as c -> sb.Append(c.ToString())
| :? int as i -> sb.Append(i.ToString())
| :? float as i -> sb.Append(i.ToString())
| _ -> printObj currObj sb (depth + 1)
sb
Это следует из документации MSDN по классу Array
:
В .NET Framework версии 2.0 класс класс Array реализует System.Collections.Generic.IList, System.Collections.Generic.ICollection, и System.Collections.Generic.IEnumerable общие интерфейсы. реализации предоставляются массивам во время выполнения, и поэтому не видны инструментам сборки документации инструментов. В результате, общие интерфейсы не появляются в синтаксисе объявления для класса Array класса, и нет никаких ссылок темы для членов интерфейса, которые доступны только путем приведения массива к общий тип интерфейса (явные реализации интерфейса). Главное о чем следует помнить, когда вы приводите массива к одному из этих интерфейсов является то. что члены, которые добавляют, вставляют или удаляют элементы, бросают NotSupportedException.
Вы уверены, что программа работает не так, как ожидалось? Интервалы отладчика не всегда надежны.