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

Вы уверены, что программа не ведет себя так, как ожидалось? Пробелы отладчика не всегда надежны.