【问题标题】:How to set an interface{} parameter by reference?如何通过引用设置 interface{} 参数?
【发布时间】:2017-12-07 21:24:36
【问题描述】:

我有一个函数,它的参数类型为interface{}。这个参数代表我的模板数据。所以在每个页面上它存储不同的数据类型(主要是结构)。我想在这个参数的数据上附加一些数据,但它是interface{} 类型,我做不到。

这是我尝试过的:

func LoadTemplate(templateData interface) {
  appendCustomData(&templateData)
  ... //other functionality that is not relevant
}

func appendCustomData(dst interface{}) {
    // ValueOf to enter reflect-land
    dstPtrValue := reflect.ValueOf(dst)
    // need the type to create a value
    dstPtrType := dstPtrValue.Type()
    // *T -> T, crashes if not a ptr
    dstType := dstPtrType.Elem()
    // the *dst in *dst = zero
    dstValue := reflect.Indirect(dstPtrValue)
    // the zero in *dst = zero
    zeroValue := reflect.Zero(dstType)
    // the = in *dst = 0

    v := reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS")
    if v.IsValid() {
        v = reflect.ValueOf("new header css value")
    }

    reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))

    //dstValue.Set(zeroValue)
    fmt.Println("new dstValue: ", dstValue)
}

我可以成功获取"HeaderCSS" 值。但我不能用另一个值替换它。我做错了什么?

我的模板数据如下所示:

我有一个通用结构:

type TemplateData struct {
    FooterJS template.HTML
    HeaderJS template.HTML
    HeaderCSS template.HTML
    //and some more
}

我还有另外一个结构体,比如:

type pageStruct struct {
    TemplateData //extends the previous struct
    Form template.HTML
    // and some other maps/string
}

我将第二个结构作为 templateData 参数发送。

现在我收到此错误:

“reflect.Value.Set using unaddressable value”在以下行:reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))

上面的代码灵感来自这个答案:https://stackoverflow.com/a/26824071/1564840

我希望能够从此界面附加/编辑值。知道我该怎么做吗?谢谢。

【问题讨论】:

  • 似乎您知道实际类型:只需对该类型键入 assert 并执行该类型支持的任何操作。如果不是:您必须显示完整的代码。 BTW:去掉空的interface{},你的代码会更简单。
  • 我不能只删除接口{},因为每个页面上的参数类型都会不同。 interface{} 是允许我接受各种参数的选项。
  • 这已经得到了很好的回答,但我想建议您也许应该添加一个SetHeaderCSS 方法并传入一个指定该方法的HeaderCSSSetter 接口。这将通过简单的更改消除所有反射,从而为您提供更好的性能、更好的可读性和编译时类型安全。
  • 好主意。谢谢!

标签: pointers go interface go-templates go-interface


【解决方案1】:

不要将指针传递给接口。相反,interface{} 值应该包含指针。只需交出这个interface{} 值:

func LoadTemplate(templateData interface) {
  appendCustomData(templateData)
  ... //other functionality that is not relevant
}

即使你不能使用比interface{}更具体的类型(因为你必须允许多种类型),你仍然可以使用type assertion,它会“超级”简单:

func appendCustomData(d interface{}) {
    if ps, ok := d.(*pageStruct); ok {
        ps.TemplateData.HeaderCSS += "+new"
    }

}

Go Playground 上试试这个。

如果您必须或想要使用反射,这就是appendCustomData() 的实现方式:

type Data struct {
    Name  string
    Age   int
    Marks []int
}

func appendCustomData(d interface{}) {
    v := reflect.ValueOf(d).Elem()

    if f := v.FieldByName("Name"); f.IsValid() {
        f.SetString(f.Interface().(string) + "2")
    }

    if f := v.FieldByName("Age"); f.IsValid() {
        f.SetInt(f.Int() + 2)
    }

    if f := v.FieldByName("Marks"); f.IsValid() {
        f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
    }

    if f := v.FieldByName("Invalid"); f.IsValid() {
        f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
    }
}

测试它:

d := &Data{
    Name:  "Bob",
    Age:   22,
    Marks: []int{5, 4, 3},
}
fmt.Printf("%+v\n", d)
appendCustomData(d)
fmt.Printf("%+v\n", d)

输出(在Go Playground 上试试):

&{Name:Bob Age:22 Marks:[5 4 3]}
&{Name:Bob2 Age:24 Marks:[5 4 3 2]}

更新:

回答您编辑的问题:传递的值是嵌入另一个结构的结构时没有区别。但是interface{} 中包裹的值仍然必须是指针。

示例appendCustomData() 将内容附加到pageStruct.TemplateData.HeaderCSS

func appendCustomData(d interface{}) {
    v := reflect.ValueOf(d).Elem()

    if f := v.FieldByName("TemplateData"); f.IsValid() {
        if f = f.FieldByName("HeaderCSS"); f.IsValid() {
            f.Set(reflect.ValueOf(f.Interface().(template.HTML) + "+new"))
        }
    }
}

测试它:

ps := &pageStruct{
    TemplateData: TemplateData{
        HeaderCSS: template.HTML("old"),
    },
}
fmt.Printf("%+v\n", ps)
appendCustomData(ps)
fmt.Printf("%+v\n", ps)

输出(在Go Playground上试试):

&{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old} Form:}
&{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old+new} Form:}

【讨论】:

  • 谷歌示例。我编辑了我的问题,添加了结构细节。就我而言,我有一个扩展另一个结构的结构。你能想出一种方法让它也适用于那个例子吗?
  • @Pascut 但是如果你知道类型,只需使用类型断言,它就容易多了。再次查看编辑后的答案。
猜你喜欢
  • 1970-01-01
  • 2020-09-09
  • 1970-01-01
  • 1970-01-01
  • 2019-08-14
  • 2020-08-22
  • 1970-01-01
  • 2022-08-05
  • 2020-01-16
相关资源
最近更新 更多