使用反射获取值的指针。

huangapple go评论71阅读模式
英文:

Get pointer to value using 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 && !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”更深层次的任何字段的地址。我真的很感激任何帮助。

英文:

I have a function that iterates through all fields of an interface passed as parameter. In order to achieve this is I am using reflection. The issue is that I do not know how to obtain the address of a non-pointer field. Here is an example:

type Z struct {
	Id int
}

type V struct {
	Id int
	F Z
}

type T struct {
	Id int
	F V
}

The above code represents my test structures. Now here is the actual function which traverses a specified structure and lists details about it:

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 &lt; val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)
		address := &quot;not-addressable&quot;
		 
		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(&quot;Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n&quot;, typeField.Name, 
			valueField.Interface(), address, typeField.Type, valueField.Kind())
		
		if valueField.Kind() == reflect.Struct {
			InspectStruct(valueField.Interface())
		}
	}
}

And here is the actual test after structure instantiation/initialization:

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)

And finally the output of InspectStruct call:

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

As you can see I am using recursion, so if one of the fields is a struct kind then I call InspectStruct for it.
My issue is that though all fields have been initialized for the entire structure "t" hierarchy, I am not able to get the address for any field located at a higher depth than "t". I would really appreciate any help.

答案1

得分: 22

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

以上是要翻译的内容。

英文:

Passing reflect.Value instead of interface{} seems to fix the problem, however I don't know why valueField.Interface() doesn't work.

Working example : 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 &lt; val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
address := &quot;not-addressable&quot;
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(&quot;0x%X&quot;, valueField.Addr().Pointer())
}
fmt.Printf(&quot;Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n&quot;, typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
if valueField.Kind() == reflect.Struct {
InspectStructV(valueField)
}
}
}
func InspectStruct(v interface{}) {
InspectStructV(reflect.ValueOf(v))
}

答案2

得分: 12

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) // 提取值
    f.i = 19
    fmt.Println(f, ms.F)            // ???
    fmt.Println(&f == &ms.F)        // 不相同!
}

然而,考虑一下interface{}返回的是什么。它包装的是ptr.F,也就是它的副本。这就是value.Interface的作用,它返回包装字段的interface{}。它不再有任何指针元数据,完全与原始结构分离。

正如你会注意到的,直接传递一个值reflect.ValueOf,对于“顶层”来说,CanAddr总是返回false,因为该地址是没有意义的,因为它会给你该值的副本的地址,更改它并不会有任何意义。(请记住,指针也是值--如果你想要指向指针类型字段(如*Foo)的地址,你实际上是在寻找**Foo)。

所以,在我们上面的例子中,如果我们天真地传入reflect.ValueOf(ExtractField(ms)),我们将得到ValueOff,它不仅没有你想要的地址,而且根据反射的说法,它甚至不可寻址(它能给你的唯一地址是Value结构中内部值副本的地址)。

那么为什么将Value传递到这个过程中会起作用呢?嗯,唯一的解释就是reflect.Value在使用ElemField时保持了必要的元数据,而interface{}则不能。所以,虽然reflect.Value可能看起来像这样:

// 免责声明:这不是 reflect.Value 的真实结构
type Value struct {
    fieldAddress uintptr
    value        Foo
}

它能给你的只有这个:

// 再次,这只是为了说明目的而对真实接口包装器进行的抽象
type interface{} struct {
    value Foo
}
英文:

The reason Interface() doesn't work is because of the interface wrapper it returns. To give an idea of what's going on, let's look at what we're doing without reflection:

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

(Playground)

However, think about the interface{} this returns. What is it wrapping? The value of ptr.F -- that is, a copy of it. This is what value.Interface does, it returns you the interface{} wrapping the field. There is no longer any pointer metadata, it's completely detached from the original struct.

As you'll note, passing a value directly to reflect.ValueOf will always return false for CanAddr for the "top tier" -- because that address is meaningless, since it would give you the address of the copy of the value, changing it wouldn't really mean anything. (Keep in mind that pointers are values too -- if you want the address of a pointer-valued field like *Foo, you're really looking for **Foo).

So, in our example above, if we were to naively pass in reflect.ValueOf(ExtractField(ms)) we'd get the ValueOf f, which not only doesn't have the address you want -- it isn't even addressable according to reflect because it would never give a valid address as far as reflect is concerned (the only address it could give you is the address of the internal value copy in the Value struct).

So why does passing the Value down the rabbit hole work? Well, the only real way to say it is that reflect.Value maintains the necessary metadata when you use Elem and Field, while the interface{} cannot. So while the reflect.Value may look like:

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

All it can give you is this

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

答案3

得分: 3

我今天深入研究了这段代码和LinearZoetrope的答案,学到了很多东西,谢谢。不过,我对你的问题得出了不同的结论,可能导致了一个更直接的解决方案:

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

2)当你通过调用"InspectStruct(valueField.Interface())"进行递归时,你传递的是嵌入结构体的值,而不是指针。

由于你是按值传递的,Go语言会创建一个临时变量,并且不允许你取地址。相反,当你进行递归时,调用valueField.Addr().Interface(),这将传递一个指向嵌入结构体的指针。

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

通过这个改变,我得到了你期望的输出:

字段名称:Id,字段值:1,地址:842350527552,字段类型:int,字段种类:int
字段名称:F,字段值:{2 {3}},地址:842350527560,字段类型:lib.V,字段种类:struct
字段名称:Id,字段值:2,地址:842350527560,字段类型:int,字段种类:int
字段名称:F,字段值:{3},地址:842350527568,字段类型:lib.Z,字段种类:struct
字段名称:Id,字段值:3,地址:842350527568,字段类型:int,字段种类:int
英文:

I went down a reflect rabbit hole today, learned a lot from studying this code and LinearZoetrope's answer, thank you. I came to a different conclusion though about your problem which led to perhaps a more straightforward solution:

  1. You're passing in a pointer to a struct when you originally call the function, but...

  2. When you recurse by calling 'InspectStruct(valueField.Interface())', rather than passing the embedded structure by pointer, you're passing it by value.

Since you're passing by value, go will create a temporary and won't let you take the address. Instead, when you recurse, call valueField.Addr().Interface(), which will pass a pointer to the embedded struct.

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

With this change, I get the output you are expecting:

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

得分: 1

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

这是因为有时候你可能会从一个零值结构体中请求接口。一旦这种情况发生,就会引发恐慌。

英文:

Answer of @OneofOne is perfect, but it is better to add one additional check

if valueField.IsValid() {
fmt.Printf(&quot;Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %v\n&quot;, typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
}

it is needed because sometimes you can ask for interface from a zero value struct. Ones it happens, it will panic.

答案5

得分: 0

一些上述的解决方案在元素不可寻址时会失败。我找到了以下方法:

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

Some of the above solutions fail if the element is not addressable. I found this way:

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

huangapple
  • 本文由 发表于 2014年6月22日 12:39:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/24348184.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定