Углубленный анализ основных принципов 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
Полное имя метода Elem
— element, что означает, что тип элемента также можно назвать указывающим типом. Аннотация требует, чтобы 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 и найдите прекрасную работу