Go语言 使用反射获取值指针

o2gm4chl  于 2023-03-21  发布在  Go
关注(0)|答案(5)|浏览(313)

我有一个函数,它会迭代一个接口的所有字段,作为参数传递。为了达到这个目的,我使用了反射。问题是我不知道如何获得一个非指针字段的地址。下面是一个例子:

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”更高深度的字段的地址。我真的很感激任何帮助。

e3bfsja2

e3bfsja21#

传递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))
}
sdnqo3pr

sdnqo3pr2#

Interface()不能工作的原因是它返回的接口 Package 器,为了给予我们了解发生了什么,让我们看看在没有反射的情况下我们在做什么:

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!
}

Playground
但是,想想这个返回的interface{},它 Package 的是什么?ptr.Fvalue--也就是它的一个副本,这就是value.Interface所做的,它返回给你 Package 字段的interface{},不再有任何指针元数据,它完全脱离了原始的结构。
正如您将注意到的,passing a value directly to reflect.ValueOf将始终为“顶层”的CanAddr返回false--因为该地址没有意义,因为它将为您提供值副本的地址,更改它实际上没有任何意义。(请记住,指针也是值--如果您想要像*Foo这样的指针值字段的地址,那么您实际上要查找的是**Foo)。
所以,在上面的例子中,如果我们简单地传入reflect.ValueOf(ExtractField(ms)),我们会得到ValueOf,它不仅没有你想要的地址--它甚至不能根据reflect寻址,因为就reflect而言,它永远不会给予有效的地址(它能给予你的唯一地址是Value结构体中内部值副本的地址)。
那么,为什么把Value传递到兔子洞里是有效的呢?好吧,唯一真实的的说法是,当您使用ElemField时,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
}
drkbr07n

drkbr07n3#

今天我陷入了一个反射兔子洞,从研究这段代码和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
pkbketx9

pkbketx94#

@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())
    }

它是必需的,因为有时候你可以从一个零值结构中请求接口。2一旦发生这种情况,它就会死机。

yvt65v4c

yvt65v4c5#

如果元素是不可寻址的,上面的一些解决方案会失败。我发现这样的方法:

iface := valueField.Interface()
ptr := reflect.NewAt(fieldVal.Type(), unsafe.Pointer(&iface))

相关问题