反映访问的地图不提供可修改的值

B. *_*lor 4 reflection dictionary go slice

语境

在编写通用差异和补丁算法的过程中,我遇到了反射问题。

当我尝试修补切片时,我没有问题,reflect.ValueOf(&slice).Elem().Index(0).CanSet()返回true。这使我可以对slice元素内的任何内容进行修补,无论是原始元素还是结构的切片。

问题

但是,当我尝试使用地图reflect.ValueOf(&map).Elem().MapIndex(reflect.ValueOf("key")).CanSet()返回false时。这样可以防止我尝试对地图内容做任何事情。

例子

切片

s := []string{"a", "b", "c"}

v := reflect.ValueOf(&s).Elem()

e := v.Index(1)

println(e.String())
println(e.CanSet())
e.Set(reflect.ValueOf("d"))

for _, v := range s {
    print(v, " ")
}

output :
b
true
a d c
Run Code Online (Sandbox Code Playgroud)

地图

m := map[string]string{
    "a": "1",
    "b": "2",
    "c": "3"}

mv := reflect.ValueOf(&m).Elem()
println(mv.MapIndex(reflect.ValueOf("a")).CanSet())

output:
false
Run Code Online (Sandbox Code Playgroud)

如何通过反射从地图中获得可修改的价值?

谢谢你的时间。

icz*_*cza 5

这不是reflect包装的限制。切片索引表达式是可寻址的(例如&s[0]有效),因此可以设置通过反射获得的切片元素。地图索引表达式不可寻址(例如&m["a"],无效),因此无法设置通过反射获得的键的值。查看相关的如何在Go中更新地图值

只有可寻址的值是可设置的,尝试“设置”不可寻址的值只能修改副本(而不是原始值),因此一开始就不允许这样做。引用自Value.CanSet()

CanSet报告v的值是否可以更改。值只能是可寻址的,并且不是通过使用未导出的结构字段获得的,才可以更改。

如果要使用反射更改分配给映射中的键的值,请使用以下Value.SetMapIndex()方法:

mv.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf("11"))
fmt.Println(m)
Run Code Online (Sandbox Code Playgroud)

输出将是(在Go Playground上尝试):

map[b:2 c:3 a:11]
Run Code Online (Sandbox Code Playgroud)

注意:不允许使用地图索引表达式的地址(并允许使用反射来更改值),因为地图的内部结构可能随时更改(例如,如果将新的键值添加到地图中,则实现可能必须重新构造它在内部存储键值对的方式),因此在&m["a"]最终使用它时,您将获得的可能的指针可能不存在(或指向另一个值)。为避免此类混乱和运行时恐慌,首先不允许这样做。