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



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

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())
}
http://play.golang.org/p/lxtmu9ydda

有人能解释一下为什么它适用于[]testEntity{}而不是[]interface{}{},如下所示:http://play.golang.org/p/HW5tXEUTlP

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

语义

原因是在Go中没有等价的语法允许你获取被interface{}值屏蔽的值的地址。

这个行为可以用一个简化的例子来演示。如果您有以下代码

slice := []testEntity{testEntity{Val: 3}}
slice[0].Val = 5
fmt.Println(slice) 

然后行

slice[0].Val = 5

实际上翻译成

(&slice[0]).Val = 5

要修改一个片元素,它必须是可寻址的,否则该值将不会被传播回片。Go会自动帮你完成。现在让我们修改这个示例,使用interface{}片:

slice := []interface{}{testEntity{Val: 3}}
slice[0].Val = 5
fmt.Println(slice) 

这个例子显然不能工作,因为interface{}没有一个名为Val的字段。因此,我们需要断言slice[0]的类型:

slice[0].(testEntity).Val = 5

虽然类型系统现在满足于这一行,但可寻址规则却不满足。现在Val的更改将丢失,因为我们正在操作slice[0]的副本。为了解决这个问题,我们必须采用slice[0]的地址,正如您将看到的,这将导致我们无处可去:

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

这个代码不能工作,因为(&slice[0])不再是interface{}类型,而是*interface{}。因此,不能将该值断言为*testEntity,因为只能断言接口值。这意味着没有Go语法用于在切片中设置接口值,并且由于反射仅模拟Go的行为,因此即使在技术上可行,这也不能与反射一起工作。

我们可以想象某些语法在保留类型的同时接受slice[0]的底层值的地址,但这种语法不存在。反思也是如此。反射知道接口的底层类型,并且可以很容易地使用该信息来提供一个类型安全的指针,指向slice[0]的底层值,因此我们可以在其上使用Set(),但是它没有,因为Go没有。

技术原因

反射包使用几个标志来标记Value对象的能力。与使用Set设置值相关的两个标志是flagAddr用于可寻址,flagRO用于将Value对象标记为只读(例如未导出的属性)。使用Set()设置时,必须取消flagRO,设置flagAddr

如果你查看reflect/value.goValue.Elem()的定义,你会发现负责将类型标志传递给新值的行:

fl := v.flag & flagRO
// ...
return Value{typ, val, fl}

在截断的v是当前值,elementValue在您的情况下。正如您所看到的,这只会复制只读标志和,而不会复制使用Value.Set()设置值所需的flagAddr 。将该行改为

fl := v.flag & (flagRO|flagAddr)

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

虽然Go不允许你合法地做你想做的事情,但从技术上讲,你仍然可以使用不安全来做。只是把这个添加到讨论中,因为你想知道如何做到这一点,为什么你不能这样做,已经回答了。

test := (*testEntity)(unsafe.Pointer(uintptr(unsafe.Pointer(&slice[0])) + 4))
test.Val = 5
fmt.Println(slice)
http://play.golang.org/p/vNdht-DMKg

您需要使用指向结构体的指针,而不是值,您不能在副本上设置。

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

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().Elem().Field(0).CanSet())
}

相关内容

  • 没有找到相关文章

最新更新