F # сопоставление с типами
Я пытаюсь рекурсивно распечатать все свойства объектов и свойства подтипа и т.д. Моя объектная модель выглядит следующим образом:
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 имеет тип string, int, float и т.д., но он всегда перескакивает в defualt case внизу. Любая идея, почему это происходит?
Ответы
Ответ 1
Вот как я получил это, чтобы работать...
let getMethod = prop.GetGetMethod()
let value = getMethod.Invoke(o, Array.empty)
ignore <|
match value with
| :? float as f -> sb.Append(f.ToString() + ", ") |> ignore
...
Ответ 2
Как указывали другие, вам нужно вызвать член GetValue
для получения значения свойства - итерация, которую вы выполнили, итерации над объектами PropertyInfo
, которые являются "дескрипторами свойства", а не фактическими значениями, Однако я не совсем понимаю, почему вы используете циклы GetEnumerator
и while
явно, когда одно и то же можно записать с помощью цикла for
.
Кроме того, вам не нужно игнорировать значение, возвращаемое вызовом sb.Append
- вы можете просто вернуть его как общий результат (потому что это 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)
Ответ 3
В вашем примере 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."
Ответ 4
Вместо этого вы хотите что-то подобное.
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 общие интерфейсы. реализации предоставляются массивы во время выполнения и, следовательно, не видимый для сборки документации инструменты. В результате общий интерфейсы не отображаются в синтаксис объявления для массива класса, и нет ссылки темы для членов интерфейса, которые доступный только путем литья массива в общий тип интерфейса (явный реализация интерфейса). Ключ что нужно знать, когда вы бросаете массив к одному из этих интерфейсов члены, которые добавляют, вставляют или удалять элементы NotSupportedException.
Ответ 5
Вы уверены, что программа не ведет себя так, как ожидалось? Пробелы отладчика не всегда надежны.