我有一个函数,它遍历作为参数传递的接口的所有字段。为了实现这一点,我正在使用反射。问题是,我不知道如何获得非指针字段的地址。下面是一个例子:
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: %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
你可以看到我使用递归,所以如果其中一个字段是一个结构类型,然后我调用InspectStruct为它。我的问题是,虽然所有字段已经初始化整个结构"t"层次结构,我无法获得位于比"t"更高深度的任何字段的地址。
通过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: %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))
}
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
传递到兔子洞中工作呢?好吧,唯一真正的说法是,当您使用Elem
和Field
时,reflect.Value
维护必要的元数据,而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
}
我今天去了一个反射兔子洞,从研究这个代码和LinearZoetrope的答案中学到了很多,谢谢。但我对你的问题得出了不同的结论,这可能会导致一个更直接的解决方案:
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: %vn", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
}
是必需的,因为有时您可以从零值结构体请求接口。一旦发生,它就会恐慌。
如果元素不可寻址,上述一些解决方案会失败。我发现这个方法:
iface := valueField.Interface()
ptr := reflect.NewAt(fieldVal.Type(), unsafe.Pointer(&iface))