反射访问的映射不提供可修改的值。

4

背景

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

当我尝试在一个切片中进行修补时,reflect.ValueOf(&slice).Elem().Index(0).CanSet() 返回 true。这使我可以修补切片元素内的任何内容,无论是原始数据类型的切片还是结构体的切片。

问题

然而,当我尝试使用映射时,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

地图

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

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

output:
false

如何通过反射从Map中获取可修改的值?
谢谢您的时间。
1个回答

6
这并不是reflect包的限制。切片索引表达式是可寻址的(例如,&s[0]是有效的),因此通过反射获取的切片元素是可设置的。映射索引表达式是不可寻址的(例如,&m["a"]是无效的),因此通过反射获取的键的值是不可设置的。请参见相关How to update map values in Go 只有可寻址的值才是可设置的,试图“设置”不可寻址的值只会修改副本(而不是原始值),因此首先不允许这样做。引用自Value.CanSet()

CanSet报告v的值是否可以更改。只有如果它是可寻址的并且没有通过未导出的结构字段来获得,才能更改值。

如果想要使用反射更改分配给映射键的值,请使用Value.SetMapIndex()方法:
mv.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf("11"))
fmt.Println(m)

输出结果将会是(请在Go Playground上尝试):
map[b:2 c:3 a:11]

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

Go语言中的Map类型是引用类型,就像指针或切片一样。那么为什么我们不能设置Maps的值呢?既然它也是引用类型。 - Himanshu
谢谢您的回答,不幸的是我需要在代码后面修改MapIndex检索到的值的内容,这就是为什么我需要这个可设置的值。有没有办法制作一个可设置的未设置值的副本? - B. Delor
@B.Delor 你可以这样做,但最终你必须调用 Value.SetMapIndex() - icza
我在想如何做到这一点,可能是我谷歌搜索的不对,但我很难找到如何将一个不可设置值的内容复制到一个新的可设置值中。^^ - B. Delor
@B.Delor 阅读此答案:如何在Go中复制接口值? 您可以使用 reflect.New() 来获得某种类型的新分配值的指针,封装在 reflect.Value 中,这将是可设置的。 - icza
显示剩余6条评论

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