获取指向使用反射来珍惜

问题描述 投票:10回答:4

我有过作为参数传递接口的所有领域迭代函数。为了实现这是我使用反射。问题是,我不知道如何获得一个非指针域的地址。下面是一个例子:

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”更高深的任何领域。我真的很感激任何帮助。

go reflection
4个回答
15
投票

路过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))
}

11
投票

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

(Qazxswpoi)

不过,想想这个返回Playground。它是什么包装? interface{}的价值 - 也就是说,它的一个副本。这是ptr.F做什么,它返回你的value.Interface包装领域。不再有任何指针的元数据,它是完全从原来的结构分离。

正如你会注意到,interface{}passing a value directly将始终返回reflect.ValueOf为CanAddr为“顶级” - 因为该地址是没有意义的,因为它会给你的价值的副本的地址,改变就不会真正的意思任何东西。 (请记住,指针值过 - 如果你想要一个像false指针值字段的地址,你真的找*Foo)。

因此,在我们上面的例子,如果我们在**Foo天真地传递我们会得到reflect.ValueOf(ExtractField(ms)) ValueOf,这不仅不会有你想要的地址 - 它甚至不是根据寻址反映,因为它永远不会放弃一个有效的地址就反映而言(唯一的地址,它可以给你的是在f结构内部的值复制的地址)。

那么,为什么不经过Value下来的兔子洞的工作?那么,唯一真正的办法可以说是Value保持必要的元数据,当您使用reflect.ValueElem,而Field不能。因此,尽管interface{}可能看起来像:

reflect.Value

所有它可以给你的是

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

2
投票

我一个反映兔子洞走到今天了,从研究这个代码和Jsor的回答学到了很多东西,谢谢。我来到了一个不同的结论,虽然你的问题而导致可能是一个更简单的解决方案:

1)你传递一个指针结构,当您最初调用函数,但是...

2)当你通过调用递归“InspectStruct(valueField.Interface())”,而不是通过指针传递的嵌入式结构,你是按值传递。

既然你是按值传递,去创建一个临时的,不会让你把地址。相反,当你递归,调用valueField.Addr()。接口(),将一个指针传递到嵌入式结构。

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

有了这个变化,我得到您所期望的输出:

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

1
投票

@OneofOne的答案是完美的,但它是最好添加一个额外的检查

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

它必要的,因为有时你可以从零结构要求的接口。问鼎它发生,它就会死机。

© www.soinside.com 2019 - 2024. All rights reserved.