Получить указатель на значение с помощью отражения

#go #reflection

#Вперед #отражение

Вопрос:

У меня есть функция, которая перебирает все поля интерфейса, переданные в качестве параметра. Для достижения этой цели я использую отражение. Проблема в том, что я не знаю, как получить адрес поля без указателя. Вот пример:

 type Z struct {
    Id int
}

type V struct {
    Id int
    F Z
}

type T struct {
    Id int
    F V
}
  

Приведенный выше код представляет мои тестовые структуры. Теперь вот фактическая функция, которая обходит указанную структуру и перечисляет сведения о ней:

 func InspectStruct(o interface{}) {
     val := reflect.ValueOf(o)
     if val.Kind() == reflect.Interface amp;amp; !val.IsNil() {
        elm := val.Elem()
        if elm.Kind() == reflect.Ptr amp;amp; !elm.IsNil() amp;amp; elm.Elem().Kind() == reflect.Ptr {
            val = elm
        }
     }
     if val.Kind() == reflect.Ptr {
        val = val.Elem()
     }

    for i := 0; i < val.NumField(); i   {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        address := "not-addressable"

        if valueField.Kind() == reflect.Interface amp;amp; !valueField.IsNil() {
            elm := valueField.Elem()
            if elm.Kind() == reflect.Ptr amp;amp; !elm.IsNil() amp;amp; elm.Elem().Kind() == reflect.Ptr {
                valueField = elm
            }
        }
        if valueField.Kind() == reflect.Ptr {
            valueField = valueField.Elem()
        }
        if valueField.CanAddr() {
            address = fmt.Sprint(valueField.Addr().Pointer())
        }

        fmt.Printf("Field Name: %s,t Field Value: %v,t Address: %vt, Field type: %vt, Field kind: %vn", typeField.Name, 
            valueField.Interface(), address, typeField.Type, valueField.Kind())

        if valueField.Kind() == reflect.Struct {
            InspectStruct(valueField.Interface())
        }
    }
}
  

И вот фактический тест после создания / инициализации структуры:

 t := new(T)
t.Id = 1
t.F = *new(V)
t.F.Id = 2
t.F.F = *new(Z)
t.F.F.Id = 3

InspectStruct(t)
  

И, наконец, вывод вызова InspectStruct:

 Field Name: Id,  Field Value: 1,     Address: 408125440 , Field type: int   , Field kind: int
Field Name: F,   Field Value: {2 {3}},   Address: 408125444 , Field type: main.V    , Field kind: struct
Field Name: Id,  Field Value: 2,     Address: not-addressable   , Field type: int   , Field kind: int
Field Name: F,   Field Value: {3},   Address: not-addressable   , Field type: main.Z    , Field kind: struct
Field Name: Id,  Field Value: 3,     Address: not-addressable   , Field type: int   , Field kind: int
  

Как вы можете видеть, я использую рекурсию, поэтому, если одно из полей имеет вид struct, я вызываю для него InspectStruct .
Моя проблема в том, что, хотя все поля были инициализированы для всей иерархии структуры «t», я не могу получить адрес для любого поля, расположенного на большей глубине, чем «t». Я был бы очень признателен за любую помощь.

Ответ №1:

Передача reflect.Value вместо interface{} , похоже, устраняет проблему, однако я не знаю, почему valueField.Interface() не работает.

Рабочий пример: http://play.golang.org/p/nleA2YWMj8

 func InspectStructV(val reflect.Value) {
    if val.Kind() == reflect.Interface amp;amp; !val.IsNil() {
        elm := val.Elem()
        if elm.Kind() == reflect.Ptr amp;amp; !elm.IsNil() amp;amp; elm.Elem().Kind() == reflect.Ptr {
            val = elm
        }
    }
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }

    for i := 0; i < val.NumField(); i   {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        address := "not-addressable"

        if valueField.Kind() == reflect.Interface amp;amp; !valueField.IsNil() {
            elm := valueField.Elem()
            if elm.Kind() == reflect.Ptr amp;amp; !elm.IsNil() amp;amp; elm.Elem().Kind() == reflect.Ptr {
                valueField = elm
            }
        }

        if valueField.Kind() == reflect.Ptr {
            valueField = valueField.Elem()

        }
        if valueField.CanAddr() {
            address = fmt.Sprintf("0x%X", valueField.Addr().Pointer())
        }

        fmt.Printf("Field Name: %s,t Field Value: %v,t Address: %vt, Field type: %vt, Field kind: %vn", typeField.Name,
            valueField.Interface(), address, typeField.Type, valueField.Kind())

        if valueField.Kind() == reflect.Struct {
            InspectStructV(valueField)
        }
    }
}

func InspectStruct(v interface{}) {
    InspectStructV(reflect.ValueOf(v))
}
  

Комментарии:

1. Я исправил одну ошибку в этом коде. Замените второе поле значения if. Вид () == отразить. Ptr { в поле значения if. Вид () == отразить. Ptr amp;amp; !valueField . IsNil() { для сохранения от паники, когда поле является указателем и нулевым

Ответ №2:

Причина Interface() , по которой это не работает, заключается в том, что она возвращает оболочку интерфейса. Чтобы дать представление о том, что происходит, давайте посмотрим, что мы делаем без отражения:

 type MyStruct struct {
    F Foo
}

type Foo struct {
    i int
}

func ExtractField(ptr *MyStruct) interface{} {
    return ptr.F
}

func main() {
    ms := amp;MyStruct{Foo{5}}
    f := ExtractField(ms).(Foo) // extract value
    f.i = 19
    fmt.Println(f, ms.F)            // ???
    fmt.Println(amp;f == amp;ms.F)        // Not the same!
}
  

(Игровая площадка)

Однако подумайте о interface{} том, что возвращает this . Что это оборачивает? Значение ptr.F — то есть его копия. Это то, что value.Interface делает, оно возвращает вам interface{} перенос поля. Больше нет метаданных указателя, он полностью отделен от исходной структуры.

Как вы заметите, передача значения напрямую в reflect.ValueOf всегда будет возвращать false для CanAddr для «верхнего уровня» — потому что этот адрес бессмыслен, поскольку он даст вам адрес копии значения, его изменение на самом деле ничего не значит. (Имейте в виду, что указатели тоже являются значениями — если вам нужен адрес поля со значением указателя *Foo , который вы действительно ищете **Foo ).

Итак, в нашем примере выше, если бы мы наивно передали, reflect.ValueOf(ExtractField(ms)) мы получили бы ValueOf f , который не только не имеет нужного вам адреса — он даже не может быть адресован в соответствии с reflect, потому что он никогда не даст действительный адрес, насколько это касается reflect (единственный адрес, который он может вам датьявляется адресом внутренней копии значения в Value структуре).

Итак, почему передача Value вниз по кроличьей норе работает? Ну, единственный реальный способ сказать это reflect.Value — сохранить необходимые метаданные при использовании Elem и Field , в то время interface{} как не может. Так что, хотя reflect.Value может выглядеть:

 // Disclaimer: not the real structure of a reflect.Value
type Value struct {
    fieldAddress uintptr
    value        Foo
}
  

Все, что он может вам дать, это

 // Again, an abstraction of the real interface wrapper 
// just for illustration purposes
type interface{} struct {
    value Foo
}
  

Комментарии:

1. Спасибо за дополнение ответа @OneOfOne. Очень признателен!

Ответ №3:

Сегодня я спустился в кроличью нору reflect, многому научился, изучив этот код и ответ LinearZoetrope, спасибо. Я пришел к другому выводу о вашей проблеме, что привело, возможно, к более прямому решению:

1) Вы передаете указатель на структуру при первоначальном вызове функции, но…

2) Когда вы выполняете рекурсию, вызывая ‘InspectStruct(valueField.Интерфейс ())’, вместо того, чтобы передавать встроенную структуру по указателю, вы передаете ее по значению.

Поскольку вы передаете по значению, go создаст временный и не позволит вам использовать адрес. Вместо этого, когда вы выполняете рекурсию, вызывайте valueField .Addr() .Интерфейс(), который передаст указатель на встроенную структуру.

     if valueField.Kind() == reflect.Struct {
-     InspectStruct(valueField.Interface())
      InspectStruct(valueField.Addr().Interface())
    }
  

С этим изменением я получаю результат, который вы ожидаете:

 Field Name: Id,  Field Value: 1,     Address: 842350527552  , Field type: int   , Field kind: int
Field Name: F,   Field Value: {2 {3}},   Address: 842350527560  , Field type: lib.V , Field kind: struct
Field Name: Id,  Field Value: 2,     Address: 842350527560  , Field type: int   , Field kind: int
Field Name: F,   Field Value: {3},   Address: 842350527568  , Field type: lib.Z , Field kind: struct
Field Name: Id,  Field Value: 3,     Address: 842350527568  , Field type: int   , Field kind: int
  

Ответ №4:

Ответ @OneOfOne идеален, но лучше добавить одну дополнительную проверку

 if valueField.IsValid() {
        fmt.Printf("Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %vn", typeField.Name,
            valueField.Interface(), address, typeField.Type, valueField.Kind())
    }
  

это необходимо, потому что иногда вы можете запросить интерфейс из структуры с нулевым значением. Если это произойдет, это вызовет панику.