Получить указатель на значение, используя отражение
У меня есть функция, которая перебирает все поля интерфейса, передаваемого в качестве параметра. Для этого я использую рефлексию. Проблема в том, что я не знаю, как получить адрес поля без указателя. Вот пример:
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 && !val.IsNil() {
elm := val.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && 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 && !valueField.IsNil() {
elm := valueField.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && 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: %v\t, Field type: %v\t, Field kind: %v\n", 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
Как вы можете видеть, я использую рекурсию, поэтому, если одно из полей является структурным, я вызываю InspectStruct для него. Моя проблема в том, что, хотя все поля были инициализированы для всей структуры "t" иерархии, я не могу получить адрес для любого поля, расположенного на большей глубине, чем "t". Я был бы очень признателен за любую помощь.
5 ответов
Переходя reflect.Value
вместо interface{}
кажется, чтобы решить проблему, однако я не знаю, почему valueField.Interface()
не работает
Рабочий пример: http://play.golang.org/p/nleA2YWMj8
func InspectStructV(val reflect.Value) {
if val.Kind() == reflect.Interface && !val.IsNil() {
elm := val.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && 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 && !valueField.IsNil() {
elm := valueField.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && 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: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
if valueField.Kind() == reflect.Struct {
InspectStructV(valueField)
}
}
}
func InspectStruct(v interface{}) {
InspectStructV(reflect.ValueOf(v))
}
Я собирался оставить это как комментарий к сообщению OneOfOne, но оно оказалось слишком сложным для комментария и очень уместно для ответа на вопрос и почему ответ OneOfOne работает правильно.
Причина Interface()
не работает из-за интерфейсной оболочки, которую он возвращает. Чтобы дать представление о том, что происходит, давайте посмотрим на то, что мы делаем, не задумываясь:
type MyStruct struct {
F Foo
}
type Foo struct {
i int
}
func ExtractField(ptr *MyStruct) interface{} {
return ptr.F
}
func main() {
ms := &MyStruct{Foo{5}}
f := ExtractField(ms).(Foo) // extract value
f.i = 19
fmt.Println(f, ms.F) // ???
fmt.Println(&f == &ms.F) // Not the same!
}
Однако подумайте о interface{}
это возвращается. Что это упаковка? Значение ptr.F
- то есть копия этого. Это то, что value.Interface
делает, он возвращает вам interface{}
обертывание поля. Больше нет метаданных указателя, они полностью отделены от исходной структуры.
Как вы заметите, передача значения непосредственно reflect.ValueOf
всегда вернется false
для CanAddr для "верхнего уровня" - потому что этот адрес не имеет смысла, так как он даст вам адрес копии значения, изменение его на самом деле ничего не будет значить. (Имейте в виду, что указатели тоже являются значениями - если вам нужен адрес поля со значением указателя, например *Foo
вы действительно ищете **Foo
).
Итак, в нашем примере выше, если бы мы были наивно передать в reflect.ValueOf(ExtractField(ms))
мы бы получили ValueOf
f
, который не только не имеет нужного вам адреса - он даже не адресуемый в соответствии с отражением, поскольку он никогда не даст действительного адреса в том, что касается отражения (единственный адрес, который он может дать вам, - это адрес внутренняя копия значения в 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) Вы передаете указатель на структуру при первоначальном вызове функции, но...
2) Когда вы выполняете рекурсию, вызывая InspectStruct(valueField.Interface()), а не передавая встроенную структуру по указателю, вы передаете ее по значению.
Поскольку вы передаете по значению, go создаст временный файл и не позволит вам взять адрес. Вместо этого, когда вы делаете рекурсивный вызов, вызывайте valueField.Addr().Interface(), который будет передавать указатель на встроенную структуру.
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
Ответ @OneofOne идеален, но лучше добавить еще одну проверку
if valueField.IsValid() {
fmt.Printf("Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %v\n", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
}
это необходимо, потому что иногда вы можете запросить интерфейс из структуры с нулевым значением. Так бывает, паникует.
Некоторые из вышеперечисленных решений не работают, если элемент не адресуется. Я нашел такой способ:
iface := valueField.Interface()
ptr := reflect.NewAt(fieldVal.Type(), unsafe.Pointer(&iface))