Углубленный анализ основных принципов Golang Reflect

Реализация пакета reflect в основном основана на выравнивании оперативной памяти и вызове переменных runtime через пакет unsafe.

Недавно, в связи с необходимостью работы, я изучил их базовую реализацию и сегодня поделился ими с вами.

Во-первых, давайте рассмотрим простой пример reflect. Методы reflect.TypeOf и reflect.ValueOf преобразуют runtime type и переменную в reflect type и переменную, полагаясь на выравнивание оперативной памяти unsafe для принудительного преобразования. reflect type и переменная такие же, как в runtime. Он может работать свободно.

Наконец, reflect.Value вызывает метод Interface() для преобразования переменной из состояния reflect обратно в состояние runtime.

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

Переменные среды выполнения.

Прежде всего, мы просто определяем тип переменной Value в соответствии с правилами Golang. Value имеет два типа атрибутов члена typ и ptr. typ — это тип, указывающий, к какому объекту относится эта переменная, а ptr — это адрес, указывающий на адрес этой переменной.

Давайте посмотрим на определение в официальном исходном коде.

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 определяет interface, поскольку разные типы имеют разные методы работы.

Например, метод getting/setting значения в Map, метод получения индекса в Slice и Array, метод отправки и получения объекта типа Chan, метод получения атрибута структуры в Struct и атрибут также могут иметь тег.

Таким образом, разные типы имеют разные уникальные методы работы. Если тип Map не может реализовать метод Index, он будет panic.

Поймите, что суть переменной — это адрес данных и тип данных, а затем работа на основе двух переменных — это reflect.

Тип отражения.

Давайте посмотрим на фрагмент кода в исходном файле reflect/type.go.

type rtype struct {
    size    uintptr
    ptrdata uintptr
    kind    uint8
    ...
}

Объект rtype — это упрощенная реализация интерфейса Type, kind — тип этого типа, а затем другие составные типы (Ptr, Slice, Map и т. д.) имеют дополнительные свойства и методы.

// ptrType represents a pointer type.
type ptrType struct {
    rtype
    elem *rtype // pointer element (pointed at) type
}

ptrType — это определение типа указателя, атрибут rtype — это тип указателя, а elem — это тип, на который указывает указатель.

Затем Ptr Type вызывает Elem для получения типа указателя и возвращает значение elem.

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

structType — это определение типа указателя, rtype — основная информация о типе структуры, а pkgPath — имя структуры.

Когда структура вызывает метод Name, возвращается pkgPath, а если метод Name вызывается с указателем на структуру, данные не возвращаются.

Поскольку pkgPath отсутствует, Elem необходимо сначала преобразовать в тип структуры, а методы Field, FieldByIndex, FieldByName, FieldByNameFunc типа структуры являются только переменными операциями с информацией fields типа структуры.

В атрибуте структуры structField, name и typ соответственно записывают имя и тип атрибута, а offsetEmbed является смещением позиции атрибута.

// chanType represents a channel type.
type chanType struct {
    rtype
    elem *rtype  // channel element type
    dir  uintptr // channel direction (ChanDir)
}

chanType — это ing типа chan, rtype — это сам chan, elem — это тип объекта операции chan и указатель знаком, а dir — обратный вход, выход, вход и выход chan.

// sliceType represents a slice type.
type sliceType struct {
    rtype
    elem *rtype // slice element type
}

sliceType — это определение типа слайса, тип слайса rtype — это его собственная информация, а elem — это тип объекта операции слайса.

// arrayType represents a fixed array type.
type arrayType struct {
    rtype
    elem  *rtype // array element type
    slice *rtype // slice type
    len   uintptr
}

arrayType — это тип массива с двумя дополнительными атрибутами на срезе, slice — это тип массива, преобразованного в слайсы, который статически определен заранее, а len — это длина массива.

В приведенном выше примере описывается определение некоторых типов, см. полный исходный код reflect.type.go.

Отражение типа.

reflect.Kind — это константа, определяющая тип отражения, который является идентификатором типа. Свойство kind объекта rtype относится к 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 
Implements(u Type) bool

Метод ChanDir просто возвращает chanType.dir. В аннотации сказано, что если тип не Chan, то он panics.

Если тип не chan, свойства dir нет и оно panics. Перед звонком в целом понятно, что Добрый — это Чан.

// ChanDir returns a channel type's direction.
// It panics if the type's Kind is not Chan.
ChanDir() ChanDir

Полное имя метода Elemelement, что означает, что тип элемента также можно назвать указывающим типом. Аннотация требует, чтобы Kind был типом Array, Chan, Map, Ptr, or Slice. Паника — это то же самое, что и метод ChanDir Чана. Доступны только эти 5 типов. elem имущество.

// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type

Глядя на предыдущие определения, вы можете знать, что elem Arry, Slice, Ptr и Chan — это тип объекта, на который указывает указатель, а map — это тип значения. Например, за следующими типами Elem следует Kind is Int.

[20]int 
[]int 
*int 
chan 
int 
map[string]int

Методы Field и NumField предназначены для получения свойств указанного индекса структуры и количества свойств структуры. В комментариях также указано, является ли Kind типом Struct или нет, потому что только [] 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 предназначен для получения типа параметра i-th, указанного этим Func.

// 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() уникален для типа карты и возвращает тип ключа карты.

// Key returns a map type's key type. 
// It panics if the type's Kind is not Map. 
Key() Type

Метод Len() уникален для вида массива и возвращает длину, определенную массивом.

// Len returns an array type's length. 
// It panics if the type's Kind is not Array. 
Len() int

Выше описан принцип реализации некоторых методов reflect.Type, а принцип остальных методов аналогичен, то есть оперировать свойствами rtype, а некоторые типы Kind имеют уникальные методы, которые можно вызывать.

Метод отражения значения.

Объект отражения Value определяет три типа атрибутов, расположение данных и флаг. Расположение памяти данных находится в расположении ptr, и метод операции должен полагаться на тип typ для определения операции типа данных.

Type — это статические данные, а Value — это динамические данные. Многие методы Value представляют собой конкретные значения, связанные с данными.

type Value struct {     
    typ *rtype     
    ptr unsafe.Pointer     
    flag 
}

Универсальная функция.

Общий метод относится к методу, который есть у всех типов. Он описывает только общую идею реализации этого метода в соответствии с определениями типа и значения. Конкретный код реализации отличается, и исходный код имеет преимущественную силу.

Метод 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

Идея метода интерфейса состоит в том, чтобы вернуть значение 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{})

Реализация метода 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

Выше описан принцип библиотеки отражения для работы с переменной времени выполнения, а переменная времени выполнения представляет собой тип плюс адрес.

В этой статье не полностью анализируется библиотека Reflect. Благодаря этим принципам вы можете примерно понять функцию и действие этих методов. Подробности смотрите в исходном коде.

Спасибо за прочтение.

Если вам нравятся такие истории и вы хотите поддержать меня, пожалуйста, хлопните мне в ладоши.

Ваша поддержка очень важна для меня — спасибо.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу