如何在接口切片上设置struct变量的值?

Dan*_*Dan 5 reflection go

如何设置使用反射中testEntity.Val包含的内容[]interface{}{}

type testEntity struct {
    Val int
}

func main() {

    slice := []interface{}{testEntity{Val: 3}}
    sliceValue := reflect.ValueOf(slice)
    elemValue := sliceValue.Index(0)

    // Prints: can set false
    fmt.Println("can set", elemValue.Elem().Field(0).CanSet())

}
Run Code Online (Sandbox Code Playgroud)

http://play.golang.org/p/lxtmu9ydda

有人可以解释为什么它可以使用[]testEntity{}但不是[]interface{}{}如下所示:http://play.golang.org/p/HW5tXEUTlP

nem*_*emo 7

根据 这个问题,这种行为是正确的.

语义

原因是Go中没有等效的语法允许您获取值掩盖的interface{}值的地址.

可以在简化示例中演示此行为.如果您有以下代码

slice := []testEntity{testEntity{Val: 3}}
slice[0].Val = 5
fmt.Println(slice) 
Run Code Online (Sandbox Code Playgroud)

那条线

slice[0].Val = 5
Run Code Online (Sandbox Code Playgroud)

实际上翻译成

(&slice[0]).Val = 5
Run Code Online (Sandbox Code Playgroud)

要修改切片元素,它必须是可寻址的,否则该值不会传播回切片.Go会自动为您完成.现在让我们修改示例并使用interface{}切片代替:

slice := []interface{}{testEntity{Val: 3}}
slice[0].Val = 5
fmt.Println(slice) 
Run Code Online (Sandbox Code Playgroud)

这个例子显然不起作用,因为interface{}没有一个字段被调用Val.所以,我们需要断言以下类型slice[0]:

slice[0].(testEntity).Val = 5
Run Code Online (Sandbox Code Playgroud)

虽然类型系统现在满足于该行,但可寻址规则不是.现在,Val由于我们正在运行副本,因此将会丢失更改slice[0].为了解决这个问题,我们必须采取地址slice[0],正如您将看到的那样,我们无处可去:

slice := []interface{}{testEntity{Val: 3}}
(&slice[0]).(*testEntity).Val = 5
fmt.Println(slice) 
Run Code Online (Sandbox Code Playgroud)

这段代码不能用,因为(&slice[0])它不再是类型interface{}*interface{}.因此,我们无法断言该值,*testEntity因为只能声明接口值.这意味着没有用于在切片中设置接口值的Go语法,并且由于反射仅模拟Go的行为,因此即使在技术上可行,这也不适用于反射.

我们可以想象一些语法slice[0]在保留类型的同时获取基础值的地址,但是这种语法不存在.反思也是如此.Reflection 知道接口的底层类型,并且可以轻松地使用该信息来提供指向slice[0]底层值的类型安全指针,以便我们可以使用Set()它,但它不会因为Go没有.

技术原因

反射包使用几个标记来标记Value对象的能力.与使用设置值相关的两个标志SetflagAddr可寻址性和flagROValue对象标记为只读(例如,未导出的属性).要设置使用Set(),flagRO必须取消设置并且flagAddr必须设置.

如果查看Value.Elem()inreflect/value.go的定义,您将找到负责将kind标志处理为新值的行:

fl := v.flag & flagRO
// ...
return Value{typ, val, fl}
Run Code Online (Sandbox Code Playgroud)

在您的情况下v,剪切是当前值elementValue.如您所见,这只会复制只读标志,而不是flagAddr使用设置值所需的标志Value.Set().将该行更改为

fl := v.flag & (flagRO|flagAddr)
Run Code Online (Sandbox Code Playgroud)

将使我们能够使用,Set()但也可以将值的基础值更改interface{}为任何其他值,从而打破类型安全.

  • 经过一番思考之后,我认为Go所具有的这种行为是一种不直观的语言怪癖.事实证明,即使不使用反射,我也无法在`[] interface {} {testEntity {Val:3}}`中更改`Val`(http://play.golang.org/p/KPYOVPfIqm)因此反射包也不允许.我可以在`[] testEntity {testEntity {Val:3}}中更改`Val`而不是`[] interface {} {testEntity {Val:3}}`这是不直观的.你怎么看? (2认同)