golang reflect實現原理

golang reflect實現原理

本文主要講述reflect庫實現的原理思路,reflect包實現具備兩個基礎unsafe操做內存對齊和runtime包的變量。

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 example

一個reflect簡單的例子,reflect.TypeOfreflect.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

先從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

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
)

reflect Type method

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類型是具備獨有方法能夠調用。

reflect.Value Method

反射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)

end

以上講述了reflect庫的原理就是操做runtime變量,而runtime變量就是一個類型加地址。

本文並無完整分析reflect庫,經過這些原理就能夠大概理解這些方法的做用和操做了,具體請參考源碼。

相關文章
相關標籤/搜索