【问题标题】:Get pointer to value using reflection使用反射获取指向值的指针
【发布时间】:2014-06-22 04:39:29
【问题描述】:

我有一个函数,它遍历作为参数传递的接口的所有字段。为了实现这一点,我正在使用反射。问题是我不知道如何获取非指针字段的地址。这是一个例子:

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


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      (Playground)

      但是,想想interface{} 这会返回。它在包裹什么? ptr.F——即它的副本。这就是value.Interface 所做的,它会返回包装字段的interface{}。不再有任何指针元数据,它与原始结构完全分离。

      您会注意到,passing a value directlyreflect.ValueOf 将始终为 CanAddr 返回 false 以表示“顶层”——因为该地址没有意义,因为它会给您提供副本的地址价值,改变它并不意味着什么。 (请记住,指针也是值——如果您想要像*Foo 这样的指针值字段的地址,那么您真的在寻找**Foo)。

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

      那么为什么将Value 传递到兔子洞会起作用呢?好吧,唯一真正的说法是reflect.Value 在您使用ElemField 时维护必要的元数据,而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
      }
      

      【讨论】:

      • 感谢您补充 @OneOfOne 的答案。非常感谢!
      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        它是必需的,因为有时您可以从零值结构中请求接口。一旦发生,它会惊慌失措。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-05-10
          • 1970-01-01
          • 1970-01-01
          • 2019-12-19
          • 1970-01-01
          • 1970-01-01
          • 2020-07-20
          • 1970-01-01
          相关资源
          最近更新 更多