1

defines

What is reflection? The definition from Wikipedia is: In computer science, reflection refers to the ability of a computer program to access, detect and modify its own state or behavior at runtime. Metaphorically speaking, reflection is the ability of a program to "observe" and modify its behavior as it runs. "Go Programming Language" is defined as follows: The Go language provides a mechanism to update variables at runtime and check their values and call their methods, but the specific types of these variables are not known at compile time. called the reflection mechanism.

Usage Scenarios and Disadvantages

There are two common scenarios for using reflection:

  • The parameter types of the function cannot be determined, and arbitrary objects need to be handled at runtime.
  • It is not possible to determine which function to call, it needs to be decided at runtime based on the parameters passed in.

The reflection mechanism can give us a lot of flexibility, but it is not recommended in most cases. The main reasons are:

  • Code related to reflection is generally less readable.
  • Reflection-related code usually exposes errors at runtime, which is often a direct panic, which may cause serious consequences.
  • Reflection has a large performance impact, running one to two orders of magnitude slower than normal code.

So this is a double-edged sword, so in order to use it well, you must understand the basic principles.

Implementation Principle

reflect.TypeOf and reflect.ValueOf are the only two "doors" into the world of reflection. As the names suggest, these two functions return the type information and value information of interface variables, respectively. Let's take a look at these two functions first.

reflect.TypeOf

func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer
}

func toType(t *rtype) Type {
    if t == nil {
        return nil
    }
    return t
}

Note: The parameters (interface type) passed into reflection functions such as TypeOf are type-converted during the compilation phase.

emptyInterface is the same as the empty interface type eface mentioned in the previous article, but the property names are different, typ is the dynamic type of the interface variable, and word is the pointer to the dynamic value. Then the logic of this function is very clear: use the unsafe.Pointer method to get the pointer value of the interface variable, cast it to the emptyInterface type, and return its dynamic type.

The return value Type is actually an interface that defines many methods to obtain various information related to the type, and *rtype implements the Type interface. Type defines a lot of methods about types, and it is recommended to go through them roughly.

type Type interface {
    // 所有的类型都可以调用下面这些函数
    // 此类型的变量对齐后所占用的字节数
    Align() int
    // 如果是 struct 的字段,对齐后占用的字节数
    FieldAlign() int
    // 返回类型方法集里的第 `i` (传入的参数)个方法
    Method(int) Method
    // 通过名称获取方法
    MethodByName(string) (Method, bool)
    // 获取类型方法集里导出的方法个数
    NumMethod() int
    // 类型名称
    Name() string
    // 返回类型所在的路径,如:encoding/base64
    PkgPath() string
    // 返回类型的大小,和 unsafe.Sizeof 功能类似
    Size() uintptr
    // 返回类型的字符串表示形式
    String() string
    // 返回类型的类型值
    Kind() Kind
    // 类型是否实现了接口 u
    Implements(u Type) bool
    // 是否可以赋值给 u
    AssignableTo(u Type) bool
    // 是否可以类型转换成 u
    ConvertibleTo(u Type) bool
    // 类型是否可以比较
    Comparable() bool
    // 下面这些函数只有特定类型可以调用
    // 如:Key, Elem 两个方法就只能是 Map 类型才能调用
    // 类型所占据的位数
    Bits() int
    // 返回通道的方向,只能是 chan 类型调用
    ChanDir() ChanDir
    // 返回类型是否是可变参数,只能是 func 类型调用
    // 比如 t 是类型 func(x int, y ... float64)
    // 那么 t.IsVariadic() == true
    IsVariadic() bool
    // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
    Elem() Type
    // 返回结构体类型的第 i 个字段,只能是结构体类型调用
    // 如果 i 超过了总字段数,就会 panic
    Field(i int) StructField
    // 返回嵌套的结构体的字段
    FieldByIndex(index []int) StructField
    // 通过字段名称获取字段
    FieldByName(name string) (StructField, bool)
    // FieldByNameFunc returns the struct field with a name
    // 返回名称符合 func 函数的字段
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 获取函数类型的第 i 个参数的类型
    In(i int) Type
    // 返回 map 的 key 类型,只能由类型 map 调用
    Key() Type
    // 返回 Array 的长度,只能由类型 Array 调用
    Len() int
    // 返回类型字段的数量,只能由类型 Struct 调用
    NumField() int
    // 返回函数类型的输入参数个数
    NumIn() int
    // 返回函数类型的返回值个数
    NumOut() int
    // 返回函数类型的第 i 个值的类型
    Out(i int) Type
    // 返回类型结构体的相同部分
    common() *rtype
    // 返回类型结构体的不同部分
    uncommon() *uncommonType
}

reflect.ValueOf

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }

    escapes(i)

    return unpackEface(i)
}

func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))
    t := e.typ
    if t == nil {
        return Value{}
    }
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

The logic of ValueOf is also relatively simple: first call escapes to let the variable i escape to the heap, then cast the variable i to emptyInterface type, and finally assemble the required information into reflect.Value type and return. The structure of reflect.Value is also similar to emptyInterface, except that there are multiple flag fields representing metadata.

There are also many methods of the reflect.Value structure, and it is recommended to have a general overview.

reflect.Set

When we want to update reflect.Value, we need to call reflect.Value.Set to update the reflection object:

func (v Value) Set(x Value) {
    v.mustBeAssignable()
    x.mustBeExported() 
    var target unsafe.Pointer
    if v.kind() == Interface {
        target = v.ptr
    }
    x = x.assignTo("reflect.Set", v.typ, target)
    if x.flag&flagIndir != 0 {
        if x.ptr == unsafe.Pointer(&zeroVal[0]) {
            typedmemclr(v.typ, v.ptr)
        } else {
            typedmemmove(v.typ, v.ptr, x.ptr)
        }
    } else {
        *(*unsafe.Pointer)(v.ptr) = x.ptr
    }
}

The code logic can be divided into four steps:

  1. Checks whether the current reflection object and its fields can be set.
  2. Check whether the Value object to be set is exportable.
  3. Call the assignTo method to create a new Value object and overwrite the original Value object.
  4. Modify the pointer value of the current reflection object according to the pointer value of the returned Value object.

There are other methods in the reflection package, so I won't list them one by one at this time. You can string up the interface, Type and Value according to the following picture:

image.png

Three Laws of

Go's official blog on reflection introduces three laws of reflection:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.

The first law of reflection is: "Reflection can get a reflective object from an interface value". When we execute reflect.ValueOf(1), although it seems that the reflection object corresponding to the basic type int is obtained, since the input parameters of the reflect.TypeOf and reflect.ValueOf methods are both interface{} types, in the method A type conversion occurred during execution.

The second law of reflection is: "You can get an interface value from a reflective object". This is the opposite of the first law. Since variables of interface type can be converted into reflection objects, other methods must be used to restore reflection objects to variables of interface type. Reflect.Value.Interface in reflect can accomplish this. item work.

The third law of reflection is: "To modify a reflective object, the value must be modifiable". This one is a bit complicated. Let's look at an example. Novices may make the following mistakes when using reflection:

func main() {
    i := 1
    v := reflect.ValueOf(i)
    v.SetInt(10)
    fmt.Println(i)
}

// 运行结果
// panic: reflect: reflect.Value.SetInt using unaddressable value

From the error message we can see that we used a non-addressable value when modifying the value of the variable i, which means that the value cannot be modified. The reason for this is that the parameters of functions in the Go language are passed by value, that is, a copy of the value is passed, and the value of the reflection object cannot be modified based on this copy. Therefore, the Go standard library makes a logical judgment on it to avoid problems.

Therefore, we can only change the value of the original variable in an indirect way: first obtain the reflect.Value corresponding to the pointer, then obtain the variable that can be set through the reflect.Value.Elem method, and finally call the Set related method to set it.


与昊
225 声望636 粉丝

IT民工,主要从事web方向,喜欢研究技术和投资之道