在go语言中,reflect.ValueOf()和Value.Elem()有什么区别?

8
我几天前开始学习golang,发现reflect.Valueof()Value.Elem()很难理解。这两个函数/方法有什么区别,如何正确使用?
这两个函数/方法都返回一个Value,并且根据go doc的说明:
ValueOf返回一个新的Value,该值初始化为存储在接口i中的具体值。ValueOf(nil)返回零值。
Elem返回接口v包含或指针v指向的值。如果v的类型不是Interface或Ptr,则会引发panic。如果v为nil,则返回零值。
我从stackoverflow上的一篇文章中找到了这段代码,但仍然不明白何时使用.Elem()。
func SetField(obj interface{}, name string, value interface{}) error {
    
    // won't work if I remove .Elem()
    structValue := reflect.ValueOf(obj).Elem()
    
    structFieldValue := structValue.FieldByName(name)

    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    structFieldType := structFieldValue.Type()

    // won't work either if I add .Elem() to the end
    val := reflect.ValueOf(value)
    if structFieldType != val.Type() {

        return fmt.Errorf("Provided value %v type %v didn't match obj field type %v",val,val.Type(),structFieldType)
    }

    structFieldValue.Set(val)
    return nil
}
1个回答

25

reflect.ValueOf() 是一个函数,可以将其视为反射的入口点。当您拥有一个“非反射”值,例如stringint时,可以使用reflect.ValueOf()获取它的reflect.Value描述符。

Value.Elem()reflect.Value 的一个方法。所以只有在已经拥有reflect.Value时才能使用它。您可以使用Value.Elem()来获取原始reflect.Value包装的值所指向的值(reflect.Value)。请注意,您也可以使用reflect.Indirect()进行此操作。 Value.Elem()还有另一种“用例”,但它更为“高级”,我们将在回答末尾返回该用例。

要“离开”反射,请使用通用的Value.Interface()方法,它将包装的值作为interface{}返回。

例如:

var i int = 3
var p *int = &i
fmt.Println(p, i)

v := reflect.ValueOf(p)
fmt.Println(v.Interface()) // This is the p pointer

v2 := v.Elem()
fmt.Println(v2.Interface()) // This is i's value: 3
这将输出(在Go Playground上进行尝试):
0x414020 3
0x414020
3

要了解Go的反射功能,请阅读Go博客:反射定律。但如果你刚开始学Go,最好先关注其他内容,把反射留给以后的冒险。

Value.Elem()的另一个用例

这是一个比较高级的话题,如果你不理解也没关系,你不需要。

我们看到了当指针被包装在reflect.Value中时,如何使用Value.Elem()来“导航”。Value.Elem()的文档说:

Elem返回接口v包含的值或指针v指向的值。

因此,如果reflect.Value包装了一个接口值,Value.Elem()还可以用于获取包装在该接口值中的具体值。

Go中的接口是它自己的主题,有关内部原理,您可以阅读Russ Cox的Go数据结构:接口。同样,这不是面向初学者的主题。

基本上,您传递给reflect.ValueOf()的任何值,如果它不是接口值,它将被隐式地包装在interface{}中。如果传递的值已经是接口值,则其中存储的具体值将作为interface{}传递。如果您传递指向接口的指针(这在Go中非常罕见!),则第二个“用例”就会出现。

因此,如果传递指向接口的指针,则该指针将被包装在一个interface{}值中。您可以使用Value.Elem()来获取指向的值,这将是一个接口值(而不是具体值),再次使用Value.Elem()将给您具体值。

以下示例说明了此过程:

var r io.Reader = os.Stdin // os.Stdin is of type *os.File which implements io.Reader

v := reflect.ValueOf(r) // r is interface wrapping *os.File value
fmt.Println(v.Type())   // *os.File

v2 := reflect.ValueOf(&r)            // pointer passed, will be wrapped in interface{}
fmt.Println(v2.Type())               // *io.Reader
fmt.Println(v2.Elem().Type())        // navigate to pointed: io.Reader (interface type)
fmt.Println(v2.Elem().Elem().Type()) // 2nd Elem(): get concrete value in interface: *os.File

请在Go Playground上尝试。


在我看来,reflect.ValueOf() 返回的值就像包含所有必要元数据的文件头,而 Value.Elem() 返回的值则是实际的文件主体? - stevenh6140
@stevenh6140 不,不完全是。 在我的第一个示例中,我有一个 int 类型的 i 变量和一个指向 i 的指针变量 p,其类型为指向 int 类型的指针。 如果你在 preflect.Value 上调用 Elem(),你会得到一个 reflect.Value,这与你在第一次调用 reflect.ValueOf() 时得到的结果相同。 - icza
@icza 很棒的回答,解决了我对反射“Value”的困惑,感谢你抽出时间写下这篇文章! - EternalObserver

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