Как вы устанавливаете значение поля структуры с помощью функции «Отражение»?


107

трудно работать с полями структуры с помощью reflectpackage. в частности, не придумали, как задать значение поля.

введите t struct {fi int; строка fs}
var rt = t {123, "jblow"}
var i64 int64 = 456
  1. получение имени поля i - похоже, это работает

    var field = reflect.TypeOf(r).Field(i).Name

  2. получение значения поля i как a) interface {}, b) int - похоже, это работает

    var iface interface{} = reflect.ValueOf(r).Field(i).Interface()

    var i int = int(reflect.ValueOf(r).Field(i).Int())

  3. установка значения поля i - попробуйте один - паника

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic : reflection.Value · SetInt с использованием значения, полученного с использованием неэкспортированного поля

    предполагая, что ему не нравятся имена полей «id» и «name», поэтому он переименован в «Id» и «Name»

    а) верно ли это предположение?

    б) если верно, то считал ненужным, поскольку в том же файле / пакете

  4. установка значения поля i - попробуйте два (с заглавными именами полей) - паника

    reflect.ValueOf(r).Field(i).SetInt( 465 )

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic : reflection.Value · SetInt с использованием неадресуемого значения


Инструкции, приведенные ниже от @peterSO, являются подробными и качественными

Четыре. это работает:

reflect.ValueOf(&r).Elem().Field(i).SetInt( i64 )

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


Ближайший пример, который я мог найти для кого-то, кто использует reflectдля установки данных, был comments.gmane.org/gmane.comp.lang.go.general/35045 , но даже там он json.Unmarshalвыполнял настоящую грязную работу
cc young

(комментарий выше устарел)
cc young

Ответы:


157

Go доступен как открытый исходный код . Хороший способ узнать об отражении - посмотреть, как его используют разработчики ядра Go. Например, пакеты Go fmt и json . В документации пакета есть ссылки на файлы исходного кода под заголовком Файлы пакета.

Пакет Go json маршалирует и демаршалирует JSON из и в структуры Go.


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

В reflectпакете Go есть CanAddrфункция.

func (v Value) CanAddr() bool

CanAddr возвращает истину, если адрес значения можно получить с помощью Addr. Такие значения называются адресными. Значение является адресуемым, если оно является элементом среза, элементом адресуемого массива, полем адресуемой структуры или результатом разыменования указателя. Если CanAddr возвращает false, вызов Addr вызовет панику.

В reflectпакете Go есть CanSetфункция, которая trueподразумевает, что CanAddrона тоже есть true.

func (v Value) CanSet() bool

CanSet возвращает истину, если значение v можно изменить. Значение можно изменить, только если оно адресуемое и не было получено с помощью неэкспортированных полей структуры. Если CanSet возвращает false, вызов Set или любого другого сеттера определенного типа (например, SetBool, SetInt64) вызовет панику.

Нам нужно убедиться, что мы можем Setэто structполе. Например,

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    // N at start
    fmt.Println(n.N)
    // pointer to struct - addressable
    ps := reflect.ValueOf(&n)
    // struct
    s := ps.Elem()
    if s.Kind() == reflect.Struct {
        // exported field
        f := s.FieldByName("N")
        if f.IsValid() {
            // A Value can be changed only if it is 
            // addressable and was not obtained by 
            // the use of unexported struct fields.
            if f.CanSet() {
                // change value of N
                if f.Kind() == reflect.Int {
                    x := int64(7)
                    if !f.OverflowInt(x) {
                        f.SetInt(x)
                    }
                }
            }
        }
    }
    // N at end
    fmt.Println(n.N)
}

Output:
42
7

Если мы можем быть уверены, что все проверки ошибок не нужны, пример упрощается до:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    fmt.Println(n.N)
    reflect.ValueOf(&n).Elem().FieldByName("N").SetInt(7)
    fmt.Println(n.N)
}

5
Сдаться! где-то там есть ответ, но четыре часа работы над json pkg мне его не дали. В случае с отражением pkg получить информацию довольно просто, но для настройки данных требуется некоторая доля черной магии, для которой я хотел бы где-нибудь увидеть простой пример!
cc young

2
Замечательно! если вы когда-нибудь будете в Таиланде, позвольте мне угостить вас пивом, двумя или тремя! большое спасибо
CC

5
Отличный практический пример, эта статья полностью демистифицировала его для меня golang.org/doc/articles/laws_of_reflection.html
danmux


Это не устанавливает поле структуры. Это устанавливает поле в указателе на структуру. Очевидно, это не ответ на вопрос, заданный OP.
wvxvw 06

14

Кажется, это работает:

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    Number int
    Text string
}

func main() {
    foo := Foo{123, "Hello"}

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))

    reflect.ValueOf(&foo).Elem().Field(0).SetInt(321)

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))
}

Печать:

123
321

Спасибо! Теперь, когда я прочитал заметки peterSO, это имеет смысл. Я использовал foo, а не & foo, поэтому его нельзя было изменить, и я не был уверен, что такое Elem ().
cc young
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.