首页 > 解决方案 > 判断任意类型的实例是否可以设置为任意类型的值

问题描述

是否可以确定(使用reflect)是否可以将任意类型的实例设置为任意值,即确定是否Value.Set()会由于类型不兼容而出现恐慌?

下面列出了 MCVE。我想知道的是“我可以set()在不使用延迟/恢复结构的情况下编写吗?”

我想避免defer不仅因为它看起来很丑,而且因为Value.Set()可能会因为其他原因而恐慌。

请注意,这不仅仅是比较类型是否相等的问题,如下o2例所示。

package main

import (
    "fmt"
    "reflect"
)

// set a value V to interface i, returning true if this can be done, else false
//
// CAN WE WRITE THIS WITHOUT HAVING TO USE DEFER / RECOVER?
//
func set(v reflect.Value, i interface{}) bool {
    success := true
    defer func() {
        if r := recover(); r != nil {
            success = false
        }
    }()
    v.Set(reflect.ValueOf(i))
    return success
}

// get the type of a typed nil
func getType(typedNil interface{}) reflect.Type {
    return reflect.TypeOf(typedNil).Elem()
}

func main() {
    t1 := getType((*int)(nil))
    o1 := reflect.New(t1)

    t2 := getType((*interface{})(nil))
    o2 := reflect.New(t2)

    var ok bool
    var aInt = 456
    var aString string = "hello"
    var aUint uint = 123

    // Set o1 to various types
    
    ok = set(o1.Elem(), aInt) // Should return true
    fmt.Printf("After o1 set to aInt returned %v: obj is type %T content '%v'\n", ok, o1.Elem().Interface(), o1.Elem().Interface())

    ok = set(o1.Elem(), aString) // Should return false
    fmt.Printf("After o1 set to aString returned %v: obj is type %T content '%v'\n", ok, o1.Elem().Interface(), o1.Elem().Interface())

    ok = set(o1.Elem(), aUint) // Should return false
    fmt.Printf("After o1 set to aUint returned %v: obj is type %T content '%v'\n", ok, o1.Elem().Interface(), o1.Elem().Interface())

    fmt.Println("")

    // Set o2 to various types
    ok = set(o2.Elem(), aInt)  // Should return true
    fmt.Printf("After o2 set to aInt returned %v: obj is type %T content '%v'\n", ok, o2.Elem().Interface(), o2.Elem().Interface())

    ok = set(o2.Elem(), aString) // Should return true
    fmt.Printf("After o2 set to aString returned %v: obj is type %T content '%v'\n", ok, o2.Elem().Interface(), o2.Elem().Interface())
    
    ok = set(o2.Elem(), aUint) // Should return true
    fmt.Printf("After o2 set to aUint returned %v: obj is type %T content '%v'\n", ok, o2.Elem().Interface(), o2.Elem().Interface())
}

标签: goreflection

解决方案


reflect.Type有一个Type.AssignableTo()方法:

// AssignableTo reports whether a value of the type is assignable to type u.
AssignableTo(u Type) bool

因此,您可以使用它来简化您的set()功能:

func set(v reflect.Value, i interface{}) bool {
    if !reflect.TypeOf(i).AssignableTo(v.Type()) {
        return false
    }
    v.Set(reflect.ValueOf(i))
    return true
}

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

After o1 set to aInt returned true: obj is type int content '456'
After o1 set to aString returned false: obj is type int content '456'
After o1 set to aUint returned false: obj is type int content '456'

After o2 set to aInt returned true: obj is type int content '456'
After o2 set to aString returned true: obj is type string content 'hello'
After o2 set to aUint returned true: obj is type uint content '123'

您还可以(另外)调用Value.CanSet()以检测某些“无法设置”的场景:

CanSet 报告是否可以更改 v 的值。只有当它是可寻址的并且不是通过使用未导出的结构字段获得时,才可以更改值。如果 CanSet 返回 false,则调用 Set 或任何特定类型的 setter(例如,SetBool、SetInt)将会恐慌。


推荐阅读