本文主要講述reflect庫實現的原理思路,reflect包實現具備兩個基礎unsafe操做內存對齊和runtime包的變量。
runtime變量是reflect的實現基礎,基於unsafe包操做runtime變量實現reflect功能。golang
首先咱們按照go的規則先簡單的定義一個變量類型Value,Value有兩個類型成員屬性typ和ptr,typ是類型表示這個變量是什麼對象,ptr是一個地址指向這個變量的地址。c#
// 若是看reflect或runtime源碼會發現二者相識,只不過被我刪了很多屬性。 type Value struct { typ Type ptr uintptr } type Type interface { Name() string // by all type Index(int) Value // by Slice Array MapIndex(value) Value // by Map Send(Value) // By Chan }
當咱們去操做一個變量時就按照Type類型來操做,而操做對象的數據就在內存的ptr位置。數組
變量類型Type定義的是一個接口,由於不一樣類型有不一樣的操做方法,例如Map的獲取/設置值,Slice和Array的獲取一個索引,Chan具備發送和接實一個對象,Struct能夠得到一個結構體屬性,屬性具備tag,這樣不一樣的類型就具備不一樣獨特的操做方法,若是Map類型調用Index方法沒法實現就會panic了。函數
理解變量本質就是一個數據地址和一個類型數據組成,而後基於者兩個變量來操做就是reflect。ui
一個reflect簡單的例子,reflect.TypeOf
和reflect.ValueOf
方法將一個runtime類型和變量轉換成reflect類型和變量,依賴unsafe操做內存對齊來強制轉換,reflect類型和變量和runtime中同樣的,就能夠實現自由操做了。this
最後reflect.Value
調用Interface()
方法將變量從reflect狀態轉換回來成runtime狀態了。google
package main import ( "fmt" "reflect" ) type Student struct { Name string Age int } func main() { s := new(Student) fmt.Println(reflect.TypeOf(s)) fmt.Println(reflect.TypeOf(s).Elem()) fmt.Println(reflect.TypeOf(*s)) v := reflect.ValueOf(s).Elem() v.Field(0).SetString("66") fmt.Printf("%#v\n", v.Interface()) }
先從reflect/type.go簡單的抄一點代碼來。rtype對象就是Type接口的簡化實現,kind就是這個類型的類型,而後其餘組合類型(Ptr、Slice、Map等)就額外添加了一些屬性和方法。指針
type rtype struct { size uintptr ptrdata uintptr kind uint8 ... }
ptrType是指針類型的定義,屬性rtype就是指針的類型,elem就是指針指向的類型,那麼一個Ptr Type調用Elem得到指針的類型就返回了elem值。code
// ptrType represents a pointer type. type ptrType struct { rtype elem *rtype // pointer element (pointed at) type }
structType是指針類型的定義,rtype是結構體類型的基礎信息,pkgPath就是結構體的名稱,當一個結構體調用Name方法時就返回了pkgPath,若是是結構體指針調用Name方法就沒有返回數據,由於沒有pkgPath須要先Elem一次轉換成結構體類型,而結構體類型的Field、FieldByIndex、FieldByName、FieldByNameFunc方法就對象結構體類型fields信息進行變量操做了。對象
而在結構體屬性structField中,name、typ分別記錄這個屬性的名稱和類型,offsetEmbed是屬性偏移位置。
// structType represents a struct type. type structType struct { rtype pkgPath name fields []structField // sorted by offset } // Struct field type structField struct { name name // name is always non-empty typ *rtype // type of field offsetEmbed uintptr // byte offset of field<<1 | isEmbedded }
chanType是chan類型的ing有,rtype是chan自己,elem是chan操做對象的類型和指針指向相識,dir就是chan的反向進、出、進出。
// chanType represents a channel type. type chanType struct { rtype elem *rtype // channel element type dir uintptr // channel direction (ChanDir) }
sliceType是切片類型定義,切片類型rtype是自己信息,elem就是切片操做的對象類型。
// sliceType represents a slice type. type sliceType struct { rtype elem *rtype // slice element type }
arrayType是數組類型,在切片上額外多了兩個屬性,slice是數組轉換成切片的類型,預先靜態定義好了,而len是數組長度。
// arrayType represents a fixed array type. type arrayType struct { rtype elem *rtype // array element type slice *rtype // slice type len uintptr }
上述example講述了部分類型的定義,完整查看源碼reflect.type.go。
method、interface、map暫未徹底看完,懂原理後不必看沒有幾行使用相關知識。
reflect.Kind是定義反射類型常量,是類型的標識。rtype的kind屬性就是指reflect.Kind。
// A Kind represents the specific kind of type that a Type represents. // The zero Kind is not a valid kind. type Kind uint const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer )
Kind方法註釋說明返回kind值就是rtype.kind,類型是reflect.Kind是go中類型的主要分類,是iota定義的類型常量。
// Kind returns the specific kind of this type. Kind() Kind
變量實現的方法定義在類型連續後面的一塊內存中,能夠unsafe讀到一個類型的所有方法,就能夠實現Implements方法判斷是否實現了一個接口了。
// Implements reports whether the type implements the interface type u. Implements(u Type) bool
ChanDir方法很簡單就返回chanType.dir,註釋說若是不是Chan類型panic了,類型不chan就沒有dir這個屬性沒法處理就panic了,在調用前通常都明確了Kind是Chan。
// ChanDir returns a channel type's direction. // It panics if the type's Kind is not Chan. ChanDir() ChanDir
Elem方法全稱是element,就是指元素類型也能夠叫指向類型,註釋要求Kind必須是Array、Chan、Map、Ptr、Slice類型否在就panic,和Chan的ChanDir方法同樣,只有這5個類型纔有elem屬性。
查看前面定義就能夠知道Arry、Slice、Ptr、Chan的elem就是指向的對象的類型,map是值的類型,例如如下類型Elem後Kind都是Int。
[20]int []int *int chan int map[string]int
// Elem returns a type's element type. // It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice. Elem() Type
Field和NumField方法是得到結構體的指定索引的屬性和結構體屬性數量,註釋同樣有說明要求Kind是Struct類型否在panic,由於就結構體類型纔有[]StructField能實現這些方法。
根據前面structType定義兩個方法的實現思路就是typ.fields[i]轉換一下和len(typ.fields).
// Field returns a struct type's i'th field. // It panics if the type's Kind is not Struct. // It panics if i is not in the range [0, NumField()). Field(i int) StructField // NumField returns a struct type's field count. // It panics if the type's Kind is not Struct. NumField() int
NumIn和In方法是Func Kind獨有的方法,NumIn返回這個Func具備多個入參,對於返回參數就是NumOut;In方法是得到這個Func指定第i參數的類型。
// NumIn returns a function type's input parameter count. // It panics if the type's Kind is not Func. NumIn() int // In returns the type of a function type's i'th input parameter. // It panics if the type's Kind is not Func. // It panics if i is not in the range [0, NumIn()). In(i int) Type
Key方法是Map Kind獨有方法,返回map鍵的類型。
// Key returns a map type's key type. // It panics if the type's Kind is not Map. Key() Type
Len方法是Array Kind獨有方法,返回Array定義的長度。
// Len returns an array type's length. // It panics if the type's Kind is not Array. Len() int
上述說明reflect.Type的部分方法實現原理,剩餘方法原理相似,就是操做rtype的屬性,部分Kind類型是具備獨有方法能夠調用。
反射Value對象定義了三個屬性 類型、數據位置、flag,數據內存位置就在ptr位置,操做方法就須要依靠typ類型來判斷數據類型操做了。
Type是靜態數據,而Value是動態數據,Value的不少方法具體值是和數據相關的。
type Value struct { typ *rtype ptr unsafe.Pointer flag }
通用方法是指全部類型具備的方法,僅說明根據Type和Value定義實現這個方法大概的思路,具體實現代碼並不同,以源碼爲準。
Type方法返回這個值的類型,大體思路就是返回v.typ,具體實現還有一些額外處理。
func (v Value) Type() Type
Kind方法實現大體思路就是返回v.typ.kind。
// Kind returns v's Kind. If v is the zero Value (IsValid returns false), // Kind returns Invalid. func (v Value) Kind() Kind
Interface 法思路就是返回v.ptr值轉換成一個interface{}變量,這樣就從reflect.Value從新轉換會變量了。
// Interface returns v's current value as an interface{}. // It is equivalent to: // var i interface{} = (v's underlying value) // It panics if the Value was obtained by accessing unexported struct fields. func (v Value) Interface() (i interface{})
Convert 方法思路就是v.ptr值轉換成參數t的類型,實現規則是Conversions語法文檔 鏡像地址。
// Convert returns the value v converted to type t. If the usual Go conversion rules do not allow conversion of the value v to type t, Convert panics. func (v Value) Convert(t Type) Value
Set 方法實現就是設置v.ptr=x.ptr,要求v和x的類型是同樣的。
同時要這個Value是CanSet,若是將一個int轉換成reflect.Value,函數傳遞的是一個值的副本,那麼再對int設置新的值就無效了,CanSet返回就是false,須要傳遞*int這樣的指針類型纔能有效設置
// Set assigns x to the value v. It panics if CanSet returns false. As in Go, x's value must be assignable to v's type. func (v Value) Set(x Value)
SetBool 方法是設置bool Kind的值,前置要求Kind是同樣的,類型還有SetInt、SetString等方法。
// SetBool sets v's underlying value. It panics if v's Kind is not Bool or if CanSet() is false. func (v Value) SetBool(x bool)
Method 返回這個值的指定索引方法。
// Method returns a function value corresponding to v's i'th method. // The arguments to a Call on the returned function should not include // a receiver; the returned function will always use v as the receiver. // Method panics if i is out of range or if v is a nil interface value. func (v Value) Method(i int) Value
Len方法返回數據數據,註釋說明要求是Array, Chan, Map, Slice, or String,前四個返回就是數據量,而String Kind返回字符串長度。
// It panics if v's Kind is not Array, Chan, Map, Slice, or String. func (v Value) Len() int
IsNil方法判斷指針是不是空,在go的實現中chan、func、interface、map、pointer、slice底層纔是指針類型,才能判斷IsNil否在panic,判斷這些指針類型的ptr是否爲0,在go代碼編寫中也只有這幾種類型能夠i==nil
這樣的比較。
在go1.13中新增了IsZero方法,判斷是不是空值,裏面這些指針類型會判斷IsNil,其餘類型就是判斷數據值是否是零值那樣。
// IsNil reports whether its argument v is nil. The argument must be // a chan, func, interface, map, pointer, or slice value; if it is // not, IsNil panics. Note that IsNil is not always equivalent to a // regular comparison with nil in Go. For example, if v was created // by calling ValueOf with an uninitialized interface variable i, // i==nil will be true but v.IsNil will panic as v will be the zero Value. func (v Value) IsNil() bool
Index方法獲取指定類型的索引,就Array、Slice、String能夠執行,否在panic,在ptr指向的位置進行一個計算獲得的偏移位置得到到索引的值。
// Index returns v's i'th element. It panics if v's Kind is not Array, Slice, or String or i is out of range. func (v Value) Index(i int) Value
Field方法是返回結構體指定索引的值,要求Kind是Struct,經過指定索引的偏移來得到這個值的地址,而後類型裏面得到到類型,最後返回索引的值。
// Field returns the i'th field of the struct v. It panics if v's Kind is not Struct or i is out of range. func (v Value) Field(i int) Value
Elem方法是返回Ptr和Interface Kind指向值,爲了解除引用。
爲何Value.Elem方法沒有了Slice、Map等類型? 具體額外獨立的操做方法Index、MapIndex等。
// Elem returns the value that the interface v contains or that the pointer v points to. // It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil. func (v Value) Elem() Value
MapIndex和MapKeys是Map Kind獨有的方法,獲取到map的索引值和所有鍵,經過typ提供的類型和ptr地址進行復雜的map操做。
// MapIndex returns the value associated with key in the map v. // It panics if v's Kind is not Map. // It returns the zero Value if key is not found in the map or if v represents a nil map. // As in Go, the key's value must be assignable to the map's key type. func (v Value) MapIndex(key Value) Value // MapKeys returns a slice containing all the keys present in the map, // in unspecified order. // It panics if v's Kind is not Map. // It returns an empty slice if v represents a nil map. func (v Value) MapKeys() []Value
Send方法是Chan Kind獨有方法,給chan放一個數據進去。
func (v Value) Send(x Value)
以上講述了reflect庫的原理就是操做runtime變量,而runtime變量就是一個類型加地址。
本文並無完整分析reflect庫,經過這些原理就能夠大概理解這些方法的做用和操做了,具體請參考源碼。