通过反射传递嵌套结构的引用参数

4
type Client struct {
    Id                int
    Age               int
    PrimaryContact    Contact
    Name              string
}

type Contact struct {
    Id        int
    ClientId  int
    IsPrimary bool
    Email     string
}

上面是一段示例代码;我要实现的是以下内容: - 使用反射循环遍历所有Client结构体字段 - 对于每个“基本”字段,使用反射设置默认值 - 对于每个结构体字段,使用递归应用上述步骤
问题在于,当对PrimaryContact字段进行内省并尝试为其任何字段设置值时,会出现以下恐慌:
reflect.Value.Set使用不可寻址的值
如果我没有弄错的话,原因是PrimaryContact是按值传递而不是按引用传递,因此当我调用其任何字段的Set方法时,它会更改副本上的字段值,而不是实际参数上的字段值。如何解决这个问题?如何使用反射将PrimaryContact字段按引用传递给我的方法?

展示你所写的代码。这是可行的。而且很可能你没有传递Contact,而是reflect.Values。请注意,在Go中,每个调用都是按值传递的,没有按引用传递的调用。 - Volker
是的,那就是我的问题,我传递了 reflect.Values,这是错误的做法。我会尝试浏览一下代码,目前它引用了其他的东西。 - Mihai H
我实际上正在传递 reflect.Value 接口。 - Mihai H
2个回答

6

我认为这是一个有趣的练习,可以练习反思。

然而,有两个指针需要注意:

  • 为了设置结构体的字段值,必须将其作为指针传递。
  • 要获取结构体字段的指针值,请使用 Value.Addr()

可行的解决方案:

package main

import (
    "fmt"
    "reflect"
    "errors"
)

type Client struct {
    Id                int
    Age               int
    PrimaryContact    Contact
    Name              string
}

type Contact struct {
    Id        int
    ClientId  int
    IsPrimary bool
    Email     string
}

func SetDefault(s interface{}) error {
    return setDefaultValue(reflect.ValueOf(s))
}

func setDefaultValue(v reflect.Value) error {

    if v.Kind() != reflect.Ptr {
        return errors.New("Not a pointer value")
    }

    v = reflect.Indirect(v)
    switch v.Kind() {
        case reflect.Int:
            v.SetInt(42)
        case reflect.String:
            v.SetString("Foo")
        case reflect.Bool:
            v.SetBool(true)
        case reflect.Struct:
            // Iterate over the struct fields
            for i := 0; i < v.NumField(); i++ {
                err := setDefaultValue(v.Field(i).Addr())
                if err != nil {
                    return err
                }
            }       

        default:
            return errors.New("Unsupported kind: " + v.Kind().String())

    }

    return nil  
}


func main() {
    a := Client{}
    err := SetDefault(&a)
    if err != nil {
        fmt.Println("Error: ", err)
    } else {
        fmt.Printf("%+v\n", a)
    }
}

输出:

{Id:42 Age:42 PrimaryContact:{Id:42 ClientId:42 IsPrimary:true Email:Foo} Name:Foo}

Playground: http://play.golang.org/p/-Mpnb7o4vl


0

我认为您只需要使用 value.Addr(),它会给您一个对联系人的引用。完整示例在此处

func main() {
    x := Client{PrimaryContact:Contact{}}
    v := reflect.ValueOf(&x)
    fmt.Println("v type:", v.Type(), ", kind:", v.Kind())
    f := v.Elem().FieldByName("PrimaryContact")
    fmt.Println("f type:", f.Type(), ", kind:", f.Kind())
    p := f.Addr()
    fmt.Println("p type:", p.Type(), ", kind:", p.Kind())
    p.Elem().FieldByName("Id").SetInt(1)
    fmt.Println("Contact Id:", x.PrimaryContact.Id)
}`

输出:

v type: *main.Client , kind: ptr
f type: main.Contact , kind: struct
p type: *main.Contact , kind: ptr
Contact Id: 1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接