Go 语言反射的实现原理( 三 )

TypeOf 函数的实现原理其实并不复杂,它只是将一个 interface{} 变量转换成了内部的 emptyInterface 表示,然后从中获取相应的类型信息 。
用于获取接口值 Value 的函数 ValueOf 实现也非常简单,在该函数中我们先调用了 escapes 函数保证当前值逃逸到堆上,然后通过 unpackEface 方法从接口中获取 Value 结构体:
?复制代码
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}}unpackEface 函数会将传入的接口 interface{} 转换成 emptyInterface 结构体然后将其中表示接口值类型、指针以及值的类型包装成 Value 结构体并返回 。
TypeOf 和 ValueOf 两个方法的实现其实都非常简单,从一个 Go 语言的基本变量中获取反射对象以及类型的过程中,TypeOf 和 ValueOf 两个方法的执行过程并不是特别的复杂,我们还需要注意基本变量到接口值的转换过程:
?复制代码
package main import ( "reflect") func main() { i := 20 _ = reflect.TypeOf(i)} $ go build -gcflags="-S -N" main.go...MOVQ $20, ""..autotmp_20+56(SP) // autotmp = 20LEAQ type.int(SB), AX // AX = type.int(SB)MOVQ AX, ""..autotmp_19+280(SP) // autotmp_19+280(SP) = type.int(SB)LEAQ ""..autotmp_20+56(SP), CX // CX = 20MOVQ CX, ""..autotmp_19+288(SP) // autotmp_19+288(SP) = 20...我们使用 -S -N 编译指令编译了上述代码,从这段截取的汇编语言中我们可以发现,在函数调用之前其实发生了类型转换,我们将 int 类型的变量转换成了占用 16 字节 autotmp_19+280(SP) ~ autotmp_19+288(SP) 的 interface{} 结构体,两个 LEAQ 指令分别获取了类型的指针 type.int(SB) 以及变量 i 所在的地址 。
总的来说,在 Go 语言的编译期间我们就完成了类型转换的工作,将变量的类型和值转换成了 interface{} 等待运行期间使用 reflect 包获取其中存储的信息 。
更新变量
当我们想要更新一个 reflect.Value 时,就需要调用 Set 方法更新反射对象,该方法会调用 mustBeAssignable 和 mustBeExported 分别检查当前反射对象是否是可以被设置的和对外暴露的公开字段:
?复制代码
func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() // do not let unexported x leak var target unsafe.Pointer if v.kind() == Interface { target = v.ptr } x = x.assignTo("reflect.Set", v.typ, target) if x.flag&flagIndir != 0 { typedmemmove(v.typ, v.ptr, x.ptr) } else { *(*unsafe.Pointer)(v.ptr) = x.ptr }}Set Set 方法中会调用 assignTo,该方法会返回一个新的 reflect.Value 反射对象,我们可以将反射对象的指针直接拷贝到被设置的反射变量上:
?复制代码
func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value { if v.flag&flagMethod != 0 { v = makeMethodValue(context, v) } switch { case directlyAssignable(dst, v.typ): fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() fl |= flag(dst.Kind()) return Value{dst, v.ptr, fl} case implements(dst, v.typ): if target == nil { target = unsafe_New(dst) } if v.Kind() == Interface && v.IsNil() { return Value{dst, nil, flag(Interface)} } x := valueInterface(v, false) if dst.NumMethod() == 0 { *(*interface{})(target) = x } else { ifaceE2I(dst, x, target) } return Value{dst, target, flagIndir | flag(Interface)} } panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String())}assignTo 会根据当前和被设置的反射对象类型创建一个新的 Value 结构体,当两个反射对象的类型是可以被直接替换时,就会直接将目标反射对象返回;如果当前反射对象是接口并且目标对象实现了接口,就会将目标对象简单包装成接口值,上述方法返回反射对象的 ptr 最终会覆盖当前反射对象中存储的值 。
实现协议
reflect 包还为我们提供了 Implements 方法用于判断某些类型是否遵循协议实现了全部的方法,在 Go 语言中想要获取结构体的类型还是比较容易的,但是想要获得接口的类型就需要比较黑魔法的方式:
?复制代码
reflect.TypeOf((*<interface>)(nil)).Elem()只有通过上述方式才能获得一个接口类型的反射对象,假设我们有以下代码,我们需要判断 CustomError 是否实现了 Go 语言标准库中的 error 协议:
?复制代码
type CustomError struct{} func (*CustomError) Error() string { return ""} func main() { typeOfError := reflect.TypeOf((*error)(nil)).Elem() customErrorPtr := reflect.TypeOf(&CustomError{}) customError := reflect.TypeOf(CustomError{}) fmt.Println(customErrorPtr.Implements(typeOfError)) // #=> true fmt.Println(customError.Implements(typeOfError)) // #=> false}


推荐阅读