golang reflect
golang reflect实现原理
runtime变量
runtime变量是reflect的实现基础,基于unsafe包操作runtime变量实现reflect功能。
首先我们按照go的规则先简单的定义一个变量类型Value,Value有两个类型成员属性typ和ptr,typ是类型表示这个变量是什么对象,ptr是一个地址指向这个变量的地址。
当我们去操作一个变量时就按照Type类型来操作,而操作对象的数据就在内存的ptr位置。
变量类型Type定义的是一个接口,因为不同类型有不同的操作方法,例如Map的获取/设置值,Slice和Array的获取一个索引,Chan具有发送和接实一个对象,Struct可以获得一个结构体属性,属性具有tag,这样不同的类型就具有不同独特的操作方法,如果Map类型调用Index方法无法实现就会panic了。
理解变量本质就是一个数据地址和一个类型数据组成,然后基于者两个变量来操作就是reflect。
reflect example
一个reflect简单的例子,reflect.TypeOf
和reflect.ValueOf
方法将一个runtime类型和变量转换成reflect类型和变量,依赖unsafe操作内存对齐来强制转换,reflect类型和变量和runtime中一样的,就可以实现自由操作了。
最后reflect.Value
调用Interface()
方法将变量从reflect状态转换回来成runtime状态了。
reflect Type
先从reflect/type.go简单的抄一点代码来。rtype对象就是Type接口的简化实现,kind就是这个类型的类型,然后其他组合类型(Ptr、Slice、Map等)就额外添加了一些属性和方法。
ptrType是指针类型的定义,属性rtype就是指针的类型,elem就是指针指向的类型,那么一个Ptr Type调用Elem获得指针的类型就返回了elem值。
structType是指针类型的定义,rtype是结构体类型的基础信息,pkgPath就是结构体的名称,当一个结构体调用Name方法时就返回了pkgPath,如果是结构体指针调用Name方法就没有返回数据,因为没有pkgPath需要先Elem一次转换成结构体类型,而结构体类型的Field、FieldByIndex、FieldByName、FieldByNameFunc方法就对象结构体类型fields信息进行变量操作了。
而在结构体属性structField中,name、typ分别记录这个属性的名称和类型,offsetEmbed是属性偏移位置。
chanType是chan类型的ing有,rtype是chan本身,elem是chan操作对象的类型和指针指向相识,dir就是chan的反向进、出、进出。
sliceType是切片类型定义,切片类型rtype是本身信息,elem就是切片操作的对象类型。
arrayType是数组类型,在切片上额外多了两个属性,slice是数组转换成切片的类型,预先静态定义好了,而len是数组长度。
上述example讲述了部分类型的定义,完整查看源码reflect.type.go。
method、interface、map暂未完全看完,懂原理后没必要看没有几行使用相关知识。
reflect Kind
reflect.Kind是定义反射类型常量,是类型的标识。rtype的kind属性就是指reflect.Kind。
reflect Type method
Kind方法注释说明返回kind值就是rtype.kind,类型是reflect.Kind是go中类型的主要分类,是iota定义的类型常量。
变量实现的方法定义在类型连续后面的一块内存中,可以unsafe读到一个类型的全部方法,就可以实现Implements方法判断是否实现了一个接口了。
ChanDir方法很简单就返回chanType.dir,注释说如果不是Chan类型panic了,类型不chan就没有dir这个属性无法处理就panic了,在调用前一般都明确了Kind是Chan。
Elem方法全称是element,就是指元素类型也可以叫指向类型,注释要求Kind必须是Array、Chan、Map、Ptr、Slice类型否在就panic,和Chan的ChanDir方法一样,只有这5个类型才有elem属性。
查看前面定义就可以知道Arry、Slice、Ptr、Chan的elem就是指向的对象的类型,map是值的类型,例如以下类型Elem后Kind都是Int。
Field和NumField方法是获得结构体的指定索引的属性和结构体属性数量,注释一样有说明要求Kind是Struct类型否在panic,因为就结构体类型才有[]StructField能实现这些方法。
根据前面structType定义两个方法的实现思路就是typ.fields[i]转换一下和len(typ.fields).
NumIn和In方法是Func Kind独有的方法,NumIn返回这个Func具有多个入参,对于返回参数就是NumOut;In方法是获得这个Func指定第i参数的类型。
Key方法是Map Kind独有方法,返回map键的类型。
Len方法是Array Kind独有方法,返回Array定义的长度。
上述说明reflect.Type的部分方法实现原理,剩余方法原理类似,就是操作rtype的属性,部分Kind类型是具有独有方法可以调用。
reflect.Value Method
反射Value对象定义了三个属性 类型、数据位置、flag,数据内存位置就在ptr位置,操作方法就需要依靠typ类型来判断数据类型操作了。
Type是静态数据,而Value是动态数据,Value的很多方法具体值是和数据相关的。
通用方法
通用方法是指所有类型具有的方法,仅说明根据Type和Value定义实现这个方法大概的思路,具体实现代码并不一样,以源码为准。
Type方法返回这个值的类型,大致思路就是返回v.typ,具体实现还有一些额外处理。
Kind方法实现大致思路就是返回v.typ.kind。
Interface 法思路就是返回v.ptr值转换成一个interface{}变量,这样就从reflect.Value重新转换成变量了。
Convert 方法思路就是v.ptr值转换成参数t的类型,实现规则是Conversions语法文档 镜像地址。
Set 方法实现就是设置v.ptr=x.ptr,要求v和x的类型是一样的。
同时要求这个Value是CanSet,如果将一个int转换成reflect.Value,函数传递的是一个值的副本,那么再对int设置新的值就无效了,CanSet返回就是false,需要传递*int这样的指针类型才能有效设置
SetBool 方法是设置bool Kind的值,前置要求Kind是一样的,类型还有SetInt、SetString等方法。
Method 返回这个值的指定索引方法。
独有方法
Len方法返回数据数量,相当于内建函数len()
,注释说明要求是Array, Chan, Map, Slice, or String,前四个返回就是数据量,而String Kind返回字符串长度。
IsNil方法判断指针是否是空,在go的实现中chan、func、interface、map、pointer、slice底层才是指针类型,才能判断IsNil否在panic,判断这些指针类型的ptr是否为0,在go代码编写中也只有这几种类型可以i==nil
这样的比较。
在go1.13中新增了IsZero方法,判断是否是空值,里面这些指针类型会判断IsNil,其他类型就是判断数据值是不是零值那样。
Index方法获取指定类型的索引,就Array、Slice、String可以执行,否在panic,在ptr指向的位置进行一个计算得到的偏移位置获得到索引的值。
Field方法是返回结构体指定索引的值,要求Kind是Struct,通过指定索引的偏移来获得这个值的地址,然后类型里面获得到类型,最后返回索引的值。
Elem方法是返回Ptr和Interface Kind指向值,为了解除引用。
为什么Value.Elem方法没有了Slice、Map等类型? 具体额外独立的操作方法Index、MapIndex等。
MapIndex和MapKeys是Map Kind独有的方法,获取到map的索引值和全部键,通过typ提供的类型和ptr地址进行复杂的map操作。
Send方法是Chan Kind独有方法,给chan放一个数据进去。
end
以上讲述了reflect库的原理就是操作runtime变量,而runtime变量就是一个类型加地址。
本文并没有完整分析reflect库,通过这些原理就可以大概理解这些方法的作用和操作了,具体请参考源码。
Last updated
Was this helpful?