4.3 反射

Kesa...大约 7 分钟golang

reflectopen in new window实现了运行时反射,有两对非常重要的函数:

  1. reflect.TypeOfopen in new window 能获取类型信息
  2. reflect.ValueOfopen in new window 能获取数据的运行时表示

以及两个重要类型:

  1. reflect.Typeopen in new window:接口类型
  2. reflect.Valueopen in new window:结构体类型
golang-reflection
golang-reflection

4.3.1 反射的三大法则

反射可以作为元编程方式减少代码,但是过多的反射会使得程序逻辑难以理解并运行缓慢。

Golang 的反射由三大法则:

  1. interface{}变量可以反射出反射对象
  2. 从反射对象可以获取interface{}变量
  3. 要修改反射对象,其值必须可以设置

interface{}中可以反射出反射对象

reflect.TypeOfopen in new windowreflect.ValueOfopen in new window入参都是interface{}类型,在方法调用的过程中,会发生隐式的类型转换

golang-interface-to-reflection
golang-interface-to-reflection

从反射对象可以获取interface{}变量

reflect.Value.Interfaceopen in new window可以将反射变量转换成interface{}变量。

golang-reflection-to-interface
golang-reflection-to-interface
v := reflect.ValueOf(1)
v.Interface().(int)
golang-bidirectional-reflection
golang-bidirectional-reflection

要修改反射对象,其值必须可以设置

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

$ go run reflect.go
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value

因为 Golang 的参数都是值传递,那么得到的反射对象和原变量没有任何关系,无法修改原变量。

若想要修改原值:

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

$ go run reflect.go
10
  1. reflect.ValueOfopen in new window传入变量的指针
  2. reflect.Value.Elemopen in new window获取指针指向的变量
  3. reflect.Value.SetIntopen in new window更新变量的值

4.3.2 类型和值

reflect.TypeOf

interface{}在反射中使用reflect.emptyInterfaceopen in new window表示:

type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}
  • typ:表示变量的类型
  • word: 表示底层的数据

reflect.TypeOfopen in new window函数会将传入的变量转换成reflect.emptyInterfaceopen in new window获取类型信息

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

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

reflect.rtypeopen in new window实现了reflect.Typeopen in new window接口,其reflect.rtype.Stringopen in new window方法可以返回类型的名称:

func (t *rtype) String() string {
	s := t.nameOff(t.str).name()
	if t.tflag&tflagExtraStar != 0 {
		return s[1:]
	}
	return s
}

reflect.ValueOf

reflect.ValueOfopen in new window会将当前值逃逸到堆上,然后通过reflect.unpackEfaceopen in new window获取reflect.Valueopen in new window

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}
}

reflect.unpackEfaceopen in new window 会将传入的接口转换成 reflect.emptyInterfaceopen in new window,然后将具体类型和指针包装成 reflect.Valueopen in new window 结构体后返回。

上述的类型转换,会在编译期间完成,将变量转换成interface{}并等待运行期间使用 reflectopen in new window 包获取接口中存储的信息。

4.3.3 更新变量

更新 reflect.Valueopen in new window 时,就需要调用 reflect.Value.Setopen in new window 更新反射对象,该方法会调用

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)
	typedmemmove(v.typ, v.ptr, x.ptr)
}

reflect.Value.assignToopen in new window会返回一个新的反射对象,这个返回的反射对象指针会直接覆盖原反射变量。

func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value {
	...
	switch {
	case directlyAssignable(dst, v.typ):
		...
		return Value{dst, v.ptr, fl}
	case implements(dst, v.typ):
		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())
}

reflect.Value.assignToopen in new window 会根据当前和被设置的反射对象类型创建一个新的 reflect.Valueopen in new window 结构体:

  • 若两个反射对象的类型是可以被直接替换,就会直接返回目标反射对象
  • 若当前反射对象是接口并且目标对象实现了接口,就会把目标对象简单包装成接口值

4.3.4 实现协议

reflect.rtype.Implementsopen in new window可以用于判断是否实现了特定的接口,使用方式:

reflect.TypeOf((*<interface>)(nil)).Elem()

例如:

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
}
  • CustomError 类型并没有实现 error 接口
  • *CustomError 指针类型实现了 error 接口

reflect.rtype.Implementsopen in new window:

func (t *rtype) Implements(u Type) bool {
	if u == nil {
		panic("reflect: nil type passed to Type.Implements")
	}
	if u.Kind() != Interface {
		panic("reflect: non-interface type passed to Type.Implements")
	}
	return implements(u.(*rtype), t)
}
func implements(T, V *rtype) bool {
	t := (*interfaceType)(unsafe.Pointer(T))
	if len(t.methods) == 0 {
		return true
	}
	...
	v := V.uncommon()
	i := 0
	vmethods := v.methods()
	for j := 0; j < int(v.mcount); j++ {
		tm := &t.methods[i]
		tmName := t.nameOff(tm.name)
		vm := vmethods[j]
		vmName := V.nameOff(vm.name)
		if vmName.name() == tmName.name() && V.typeOff(vm.mtyp) == t.typeOff(tm.typ) {
			if i++; i >= len(t.methods) {
				return true
			}
		}
	}
	return false
}
  • 若接口中不含任何方法,则是空接口,直接返回true
  • 遍历接口方法数组,判断是否实现了所有的接口,时间复杂度O(n)
golang-type-implements-interface
golang-type-implements-interface

4.3.5 方法调用

通过反射进行方法调用比较复杂:

func Add(a, b int) int { return a + b }

func main() {
	v := reflect.ValueOf(Add)
	if v.Kind() != reflect.Func {
		return
	}
	t := v.Type()
	argv := make([]reflect.Value, t.NumIn())
	for i := range argv {
		if t.In(i).Kind() != reflect.Int {
			return
		}
		argv[i] = reflect.ValueOf(i)
	}
	result := v.Call(argv)
	if len(result) != 1 || result[0].Kind() != reflect.Int {
		return
	}
	fmt.Println(result[0].Int()) // #=> 1
}

其中reflect.Value.Callopen in new window

func (v Value) Call(in []Value) []Value {
	v.mustBe(Func)
	v.mustBeExported()
	return v.call("Call", in)
}

运行时调用方法的入口:

reflect.Value.callopen in new window将分为几个流程:

  1. 检查输入参数以及类型的合法性
  2. 将传入的 reflect.Valueopen in new window 参数数组设置到栈上
  3. 通过函数指针和输入参数调用函数;
  4. 从栈上获取函数的返回值

参数检查

func (v Value) call(op string, in []Value) []Value {
	t := (*funcType)(unsafe.Pointer(v.typ))
	...
	if v.flag&flagMethod != 0 {
		rcvr = v
		rcvrtype, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
	} else {
		...
	}
	n := t.NumIn()
	if len(in) < n {
		panic("reflect: Call with too few input arguments")
	}
	if len(in) > n {
		panic("reflect: Call with too many input arguments")
	}
	for i := 0; i < n; i++ {
		if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) {
			panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String())
		}
	}
  • 从反射对象中取出当前的函数指针 unsafe.Pointer,如果该函数指针是方法,那么我们会通过 reflect.methodReceiveropen in new window 获取方法的接收者和函数指针
  • 检查传入参数的个数以及参数的类型与函数签名中的类型是否可以匹配,任何参数的不匹配都会导致整个程序的崩溃中止

准备参数

nout := t.NumOut()
	frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype)

	var args unsafe.Pointer
	if nout == 0 {
		args = framePool.Get().(unsafe.Pointer)
	} else {
		args = unsafe_New(frametype)
	}
	off := uintptr(0)
	if rcvrtype != nil {
		storeRcvr(rcvr, args)
		off = ptrSize
	}
	for i, v := range in {
		targ := t.In(i).(*rtype)
		a := uintptr(targ.align)
		off = (off + a - 1) &^ (a - 1)
		n := targ.size
		...
		addr := add(args, off, "n > 0")
		v = v.assignTo("reflect.Value.Call", targ, addr)
		*(*unsafe.Pointer)(addr) = v.ptr
		off += n
	}
  1. 通过 reflect.funcLayoutopen in new window 计算当前函数需要的参数和返回值的栈布局,也就是每一个参数和返回值所占的空间大小;
  2. 如果当前函数有返回值,需要为当前函数的参数和返回值分配一片内存空间 args
  3. 如果当前函数是方法,需要向将方法的接收接收者者拷贝到 args 内存中
  4. 将所有函数的参数按照顺序依次拷贝到对应 args 内存中
    1. 使用 reflect.funcLayoutopen in new window 返回的参数计算参数在内存中的位置
    2. 将参数拷贝到内存空间中

调用函数

准备好调用函数需要的全部参数后向函数reflect.reflectcallopen in new window 传入栈类型、函数指针、参数和返回值的内存空间、栈的大小以及返回值的偏移量。

Reference

  1. https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/open in new window
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2