Язык Go: Законы отражения Китайская версия

задняя часть Go WeChat API

Перевел статью об отражении в официальном блоге Go:

Введение

В информатике отражение — это способность обнаруживать собственную структуру (тип) во время выполнения, а отражение составляет основу метапрограммирования и источник путаницы.

В этой статье мы попытаемся разъяснить, как работает отражение в Go, каждый язык имеет свою модель отражения (обычно Java), а многие языки даже не поддерживают отражение, поэтому в этой статье я просто опишу отражение в Go.

Типы и интерфейсы

Поскольку вся модель отражения построена на системе типов, давайте сначала рассмотрим типы в Go.

Go — статически типизированный язык, любая переменная имеет явный тип во время компиляции, напримерint、float32、*MyType, []byteи т.п. типа...

type MyInt int

var i int
var j MyInt

Переменнаяiимеет типint,Переменнаяjимеет типMyInt. Они оба имеют различные статические типы, но иначе имеют одинаковый базовый типint. Поскольку статические типы различаются, их необходимо преобразовать, прежде чем их можно будет назначить.

Тип интерфейса — очень важная категория в системе типов, которая представляет собой сжатый набор методов. Переменная интерфейса может хранить любое значение, если это значение реализует соответствующий набор методов интерфейса.ioв упаковкеio.Reader 和 io.WriterИнтерфейсы — хорошо известный пример.

// Reader is the interface that wraps the basic Read method.
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
    Write(p []byte) (n int, err error)
}

любого типа, если он реализуетReadилиWriteспособ реализоватьio.Readerилиio.Writerинтерфейс. Значение: тип интерфейсаio.ReaderМожет быть назначена любая реализацияReadТип метода.

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

выяснить переменныеrВнутреннее поведение — очень важная вещь, прежде всегоrТип всегдаio.ReaderПричина очень проста, Go - статически напечатанный язык,rТип уже определен при компиляцииio.Reader.

Важным примером типа интерфейса является пустой интерфейс.interface{}:

interface{}

Пустой набор методов означает, что любой тип реализует пустой интерфейс,Любому типу может быть присвоено значение.

Некоторые люди говорят, что интерфейсы динамически типизированы, но это не так, они статически типизированы. Переменная типа интерфейса всегда имеет фиксированный статический тип, даже если значение, хранящееся в интерфейсе во время выполнения, имеет другой тип (тип реализует набор методов интерфейса).

Нам необходимо понимать эти концепции, потому что отражение и интерфейсы тесно связаны.

значение интерфейса

Расс Кокс написал статьюGo Data Structures: InterfacesПодробно объясняются интерфейсные значения языка Go. Опять же, не повторяя понятий в статье, вот краткое содержание статьи:

Переменная типа интерфейса хранит пару значений:

  • value: фактическое значение, присвоенное переменной типа интерфейса;
  • type: Информация о типе фактического значения.
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

Переменная типа интерфейсаrВключают(value, type)правильно(tty, *os.File). тип*os.FileСпособы добиться большего, чемRead, даже если текущий интерфейс предоставляет толькоReadметод, значение внутри интерфейса несет всю информацию о типе значения, поэтому мы можем реализовать следующие операции (информация о типе, естественно, утверждается):

var w io.Writer
w = r.(io.Writer)

Приведенное выше выражение присваивания называется утверждением типа, его утверждениеrВнутренне хранится в переменных интерфейса(value, type)выполнитьio.Writerинтерфейс, поэтому мы можем назначить егоw. После того, как задание выполнено,wВключают(tty, *os.File), С намиrтак же, как видно в. Статический тип интерфейса определяет, какие методы могут быть вызваны через переменную интерфейса, даже если внутренне сохраненный(value, type)Имеет больший набор методов.

Двигаясь дальше, мы также можем сделать это:

var empty interface{}
empty = w

наш пустой интерфейсemptyбудет по-прежнему хранить то же самое внутри(tty, *os.File).这意味着空接口可以存储任何值并拥有我们需要的所有信息。

Там нет утверждения типа при назначении значения для пустого интерфейса, потому что любое значение удовлетворяет интерфейсу воздуха,wОчевидно, реализует пустой интерфейс (набор методов является надмножеством пустого интерфейса). в то время как вышеReaderпреобразованныйWriterне то же самое, нам нужно явно использовать утверждение типа, потому чтоReaderинтерфейс неWriterРасширенный набор интерфейсов.

Важной деталью является то, что интерфейс всегда хранится (значение, тип бетона) и не может хранить (значение, тип интерфейса), а интерфейс не хранит значения интерфейса!

Теперь мы готовы изучать отражение.

первый закон отражения

Отражение извлекает объекты отражения из значений интерфейса.

В самом основном понятии,Отражение — это просто механизм для определения типа и значения, хранящегося в интерфейсе.. Итак, нам нужно понятьreflectдва вида в упаковкеTypeиValue. Эти два типа обеспечивают доступ к внутреннему хранению переменных интерфейса и обеспечивают две простые функцииTypeOfиValueOfПолучить из переменной интерфейсаTypeиValue(отValueполучитьTypeЭто тоже очень простая вещь, мы временно оставляем их концептуально разделенными)

ДавайтеTypeOfНачинать:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
}

вывод:

type: float64

Вам может быть интересно, где находится интерфейс, похоже, он только проходитfloat64Типxпеременная в качестве параметраTypeOfфункции, а не переменные интерфейса. ФактическиTypeOfПараметр функции подписи пустой интерфейс,xСначала он будет назначен пустой интерфейс, а затем передан как параметр функции,TypeOfФункция внутренне обрабатывает пустой интерфейс, чтобы восстановить информацию о типе.Type.

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type

ValueOfАналогично получается и функцияValueпеременная типа.

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())

вывод:

value: <float64 Value>

позвонить напрямуюStringметод, потому что по умолчаниюfmtПакеты погружаются прямо вValueОтображает внутреннее истинное значение (3.4).

TypeиValueоба содержат множество методов для их обнаружения и манипулирования ими, важным из которых являетсяValueизTypeметод возвращает соответствующийTypeвведите значение. Другой важный способ - иметь обаKindметод возвращает константный примитивный тип (Uint、Float64、SliceЖдать). как правилоValueВверхInt,FloatРоль функции заключается в извлечении значения, хранящегося внутри.

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

вывод:

type: float64
kind is float64: true
value: 3.4

иметь что-тоSetIntиSetFloatметоды класса, их использование должно быть понятонастраиваемыйконцепций, третий закон отражения ниже говорит о них подробно.

В библиотеке отражений есть несколько концепций, о которых стоит поговорить отдельно.

  1. Чтобы API был простым иValueТипgetterиsetterНаборы методов могут оперировать относительно большими значениями, используются все целые числа без знака.int64как параметр и возвращаемое значение. какIntметод возвращаетint64значение типа,SetIntиспользоватьint64тип параметра.
var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())                            // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint())                                       // v.Uint returns a uint64.
  1. KindМетод возвращает базовый тип, соответствующий статическому типу, например следующийxСтатический типMyIntтип, базовый типreflect.Int.
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

второй закон отражения

Отражение извлекает значения интерфейса из объекта отражения.

Точно так же, как закон физического рефлекса, значение границы раздела может быть получено из обратного направления отражающего объекта.

пройти черезValueизInterfaceМетод может восстановить значение интерфейса, на самом деле этот метод упаковываетtypeиvalueИнформация возвращается в пустой интерфейс.

// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

В результате мы можем добиться:

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)

путем отражения объектаvРаспечататьfloat64ценность.

мы можем использоватьfmt.Println、fmt.PrintfЛучше работают и т. д. функции, которые принимают в качестве аргументов пустые значения интерфейса и распаковывают эти аргументы, как вы узнали выше. Итак, если вы хотите печатать напрямуюreflect.Valueнужно использовать контентInterfaceФункция передается после получения значения интерфейса.

fmt.Println(v.Interface())

Почему бы просто не использоватьfmt.Println(v)? так какvдаreflect.ValueТип значения, который нам нужен, — это значение, которое фактически сохраняется.

fmt.Printf("value is %7.1e\n", v.Interface())

вывод

3.4e+00

Опять же, здесь нет необходимости использовать утверждения типов.v.Interface()прибытьfloat64Это связано с тем, что значение и тип, хранящиеся внутри пустого интерфейса,PrintfВнутри функция будет восстановлена.

короче,InterfaceпутьValueOfобратный метод, за исключением того, что возвращаемое значение всегда имеет статический типinterface{}.

третий закон отражения

Чтобы изменить объект отражения, значение должно быть устанавливаемым.

Статья 3. Закон очень легко запутать, а если начать понимать с первого принципа, то он проще.

Вот некоторые из них не работают, но стоит изучить код:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

Если вы разрешите эти коды, он будет генерироватьpanicОшибка.

panic: reflect.Value.SetFloat using unaddressable value

Ошибка не говорит о том, что значение 7.1 равноnot addressable, но скажиvне настраивается, настраиваетсяValueважные свойства, не всеValueвсе настраиваемые.

CanSetМетод проверяет, является ли значение устанавливаемым.

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())

вывод:

settability of v: false

в неустановленномValueзвонитьSetМетод пойдет не так, так что же можно установить?

Установленность немного похожа на адресативность, строго говоря: ** Это свойство отражающего объекта, который может изменять значение, которое фактически создало отражающий объект. Установлена ​​зависит от того, имеет ли отражательный объект исходное значение (указатель).

var x float64 = 3.4
v := reflect.ValueOf(x)

Когда мы передаем копию x вreflect.ValueOf, поэтому пустое интерфейсное значение параметра сохраняется внутриxкопировать вместоxсам.

v.SetFloat(7.1)

Следовательно, если этот оператор выполняется успешно, он не будет обновляться.x,Несмотря на тоvПохоже, сквозьxСозданный. Но будет обновляться сохраненоValueскопировать значение в , правдаxне пострадал. Вышеупомянутая ситуация склонна к путанице и путанице, поэтому такое поведение определяется как недопустимое на уровне языка, и этой проблемы можно избежать, установив атрибуты на основе суждения.

Если приведенное выше выглядит странно, это не так, это просто странная оболочка для знакомых ситуаций (передача по значению и передача по указателю, вы можете изменить исходное значение только при получении указателя).

передача мыслиxк функции.

f(x)

мы не ожидалиfможно изменитьxзначение, так как мы переходим кfдаxкопия значения вместоxсам. Если мы хотим изменить напрямуюx, мы должны пройтиxадрес (указатель).

f(&x)

Вышеупомянутый способ очень прост и понятен, и принцип отражения такой же. Если мы хотим изменить путем отраженияx, мы должны передать указатель наValue.

var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())

вывод:

type of p: *float64
settability of p: false

объект отраженияpВсе еще не устанавливается, но мы не хотим его изменятьpЗначение указателя, на самом деле то, что мы хотим изменить,*pкоторыйpзначение, на которое указывает. нам нужно позвонитьElemметод,Он косвенно принимает исходное значение через указатель и сохраняет результат в новомValueвозвращение в стоимости.

v := p.Elem()
fmt.Println("settability of v:", v.CanSet())

в настоящее времяvявляется устанавливаемым объектом отражения.

settability of v: true

посколькуvначать представлятьx, мы наконец можем использоватьv.SetFloatМодификация методаxЗначение:

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

вывод:

7.1
7.1

Хотя рефлексию немного сложно понять, все, что она делает, поддерживается на уровне языка, может быть.ValueиTypeскроет случившееся. Просто бодрствуйте и обратите вниманиеValueПри изменении должен указывать на адрес.

Structs

В приведенном выше примереvПросто указатель на примитивный тип, и более общая проблема заключается в изменении полей структуры, когда у нас есть указатель на структуру, мы можем изменить значения ее полей.

Ниже приведен простой пример разбора значения структуры. использоватьTСоздайте указатель типаValueИ поэтому могут быть изменены в последующемt.

объявить и инициализироватьtypeOfTв видеtтип, и с помощью прямого методаNumFieldиFieldИзвлеките имя поля, тип и значение.

  • ValueизFieldвсе ещеValue, и устанавливается;
  • TypeизFieldявляетсяStructField.
type T struct {
    A int
    B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
}

вывод:

0: A int = 23
1: B string = skidoo

Еще одно знание о settable: только поля, начинающиеся с прописной буквы, являются setable (экспортируемые поля).

так какsСодержит устанавливаемый объект отражения (Elem получает исходный объект), и мы можем изменять поля структуры.

s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

вывод:

t is now {77 Sunset Strip}

о