การใช้การสะท้อนคุณกำหนดค่าของฟิลด์โครงสร้างอย่างไร


107

มีเวลาคร่าวๆในการทำงานกับฟิลด์โครงสร้างโดยใช้reflectแพ็คเกจ โดยเฉพาะอย่างยิ่งยังไม่ได้หาวิธีตั้งค่าฟิลด์

พิมพ์ t struct {fi int; fs string}
var rt = เสื้อ {123, "jblow"}
var i64 int64 = 456
  1. รับชื่อฟิลด์ i - ดูเหมือนจะใช้ได้

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

  2. รับค่าของฟิลด์ i เป็น a) อินเทอร์เฟซ {}, b) int - ดูเหมือนจะใช้ได้

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

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

  3. การตั้งค่าของฟิลด์ฉัน - ลองหนึ่ง - ตกใจ

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

    ความตื่นตระหนก : reflect.Value · SetInt โดยใช้ค่าที่ได้รับจากฟิลด์ที่ไม่ได้ส่งออก

    สมมติว่าไม่ชอบชื่อฟิลด์ "id" และ "name" จึงเปลี่ยนชื่อเป็น "Id" และ "Name"

    ก) สมมติฐานนี้ถูกต้องหรือไม่?

    b) ถ้าถูกต้องคิดว่าไม่จำเป็นเนื่องจากอยู่ในไฟล์ / แพ็คเกจเดียวกัน

  4. การตั้งค่าของฟิลด์ i - ลองสอง (โดยใช้ชื่อฟิลด์เป็นตัวพิมพ์ใหญ่) - ตกใจ

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

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

    panic : reflect.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

คำตอบ:


158

Go สามารถใช้ได้เป็นรหัสที่มาเปิด วิธีที่ดีในการเรียนรู้เกี่ยวกับการสะท้อนกลับคือการดูว่านักพัฒนา Go หลักใช้มันอย่างไร ตัวอย่างเช่นแพ็คเกจGo fmtและjson เอกสารประกอบแพ็กเกจมีลิงก์ไปยังไฟล์ซอร์สโค้ดภายใต้หัวข้อไฟล์แพ็คเกจ

แพคเกจ Go json จัดระเบียบและ unmarshals JSON จากและไปยังโครงสร้าง Go


นี่คือตัวอย่างทีละขั้นตอนซึ่งกำหนดค่าของstructฟิลด์ในขณะที่หลีกเลี่ยงข้อผิดพลาดอย่างระมัดระวัง

reflectแพ็กเกจGo มีCanAddrฟังก์ชัน

func (v Value) CanAddr() bool

CanAddr ส่งคืนจริงหากสามารถรับแอดเดรสของค่าได้ด้วย Addr ค่าดังกล่าวเรียกว่าแอดเดรส ค่าสามารถระบุแอดเดรสได้หากเป็นองค์ประกอบของสไลซ์องค์ประกอบของอาร์เรย์แอดเดรสฟิลด์ของโครงสร้างแอดเดรสหรือผลลัพธ์ของการยกเลิกการอ้างอิงตัวชี้ หาก CanAddr ส่งคืนเท็จการเรียก Addr จะตกใจ

reflectแพ็คเกจGo มีCanSetฟังก์ชั่นซึ่งถ้าtrueบอกว่าCanAddrเป็นเช่นtrueนั้น

func (v Value) CanSet() bool

CanSet จะคืนค่าจริงหากค่าของ v สามารถเปลี่ยนแปลงได้ ค่าสามารถเปลี่ยนแปลงได้ก็ต่อเมื่อสามารถระบุแอดเดรสได้และไม่ได้รับโดยการใช้ฟิลด์โครงสร้างที่ไม่ได้ส่งออก หาก CanSet ส่งคืนเท็จการเรียก Set หรือ setter เฉพาะประเภทใด ๆ (เช่น 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 young

5
ตัวอย่างที่ดีในทางปฏิบัติบทความนี้เข้าใจผิดอย่างสมบูรณ์สำหรับฉันgolang.org/doc/articles/laws_of_reflection.html
danmux

ตัวอย่างที่ยอดเยี่ยมนี่คือตัวอย่างสนามเด็กเล่นของรหัสเดียวกันplay.golang.org/p/RK8jR_9rPh
Sarath Sadasivan Pillai

ไม่ได้ตั้งค่าฟิลด์โครงสร้าง สิ่งนี้ตั้งค่าฟิลด์ในตัวชี้เป็นโครงสร้าง เห็นได้ชัดว่านี่ไม่ใช่การตอบคำถามที่ OP ถาม
wvxvw

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.