f # сопоставление с типами - PullRequest
12 голосов
/ 07 июня 2010

Я пытаюсь рекурсивно распечатать все свойства объектов, свойства подтипа и т. Д. Моя объектная модель выглядит следующим образом ...

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 и т. Д., Но он всегда переходит к регистру по умолчанию внизу. Есть идеи, почему это происходит?

Ответы [ 5 ]

15 голосов
/ 08 июня 2010

Как уже отмечали другие, вам нужно вызвать элемент 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) 
6 голосов
/ 09 июня 2010

Вот как я заставил это работать ...

 let getMethod = prop.GetGetMethod()
 let value = getMethod.Invoke(o, Array.empty)
     ignore <|
         match value with
         | :? float as f -> sb.Append(f.ToString() + ", ") |> ignore
                            ...
1 голос
/ 08 июня 2010

В вашем примере enumer.Current - это объект, содержащий PropertyInfo. Это означает, что currObj всегда является объектом PropertyInfo и всегда будет соответствовать последнему регистру в вашем выражении match.

Поскольку вас интересует тип значения свойства, вам необходимо вызвать метод 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."
0 голосов
/ 07 июня 2010

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

0 голосов
/ 07 июня 2010

Вместо этого вы хотите что-то подобное.

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.

...