Skip to content

Reflection

Reflection เป็นกลไกที่ตรวจสอบโครงสร้างของภาษาเองขณะรันไทม์ มันสามารถจัดการกับปัญหาบางอย่างได้อย่างยืดหยุ่น แต่ในขณะเดียวกันข้อเสียที่นำมาซึ่งก็ชัดเจนเช่นกัน เช่น ปัญหาประสิทธิภาพ เป็นต้น ใน Go นั้น reflection มีความสัมพันธ์อย่างใกล้ชิดกับ interface{} ในระดับมากแล้ว ทุกที่ที่มี interface{} ปรากฏ ก็จะมี reflection อยู่ด้วย API reflection ใน Go จัดเตรียมโดยแพ็กเกจมาตรฐาน reflect

Interface

ก่อนเริ่มต้น ให้ทำความเข้าใจเบื้องต้นเกี่ยวกับสอง interface ที่อยู่ในแพ็กเกจ runtime ใน Go นั้น interface โดยพื้นฐานแล้วคือ struct Go แบ่ง interface ออกเป็นสองประเภทใหญ่ๆ ขณะรันไทม์ ประเภทหนึ่งคือ interface ที่ไม่มี method set อีกประเภทหนึ่งคือ interface ที่มี method set สำหรับ interface ที่มี method set นั้น ขณะรันไทม์จะถูกแสดงโดย struct iface ดังนี้

go
type iface struct {
   tab  *itab // ประกอบด้วย ประเภทข้อมูล, ประเภท interface, method set ฯลฯ
   data unsafe.Pointer // พอยน์เตอร์ที่ชี้ไปยังค่า
}

สำหรับ interface ที่ไม่มี method set นั้น ขณะรันไทม์จะถูกแสดงโดย struct eface ดังนี้

go
type eface struct {
   _type *_type // ประเภท
   data  unsafe.Pointer // พอยน์เตอร์ที่ชี้ไปยังค่า
}

และสอง struct นี้ในแพ็กเกจ reflect มีประเภท struct ที่สอดคล้องกัน iface สอดคล้องกับ nonEmptyInterface

go
type nonEmptyInterface struct {
  itab *struct {
    ityp *rtype // ประเภท interface แบบ static
    typ  *rtype // ประเภท concrete แบบ dynamic
    hash uint32 // hash ประเภท
    _    [4]byte
    fun  [100000]unsafe.Pointer // method set
  }
  word unsafe.Pointer // พอยน์เตอร์ที่ชี้ไปยังค่า
}

และ eface สอดคล้องกับ emptyInterface

go
type emptyInterface struct {
   typ  *rtype // ประเภท concrete แบบ dynamic
   word unsafe.Pointer // ค่าที่ชี้ไปยังพอยน์เตอร์
}

สำหรับสองประเภทนี้ ทางทางการได้ให้นิยามที่ชัดเจนมาก

  • nonEmptyInterface: nonEmptyInterface is the header for an interface value with methods
  • emptyInterface: emptyInterface is the header for an interface{} value

ข้างต้นได้กล่าวถึงคำว่า dynamic concrete type ต้นฉบับคือ dynamic concrete type ก่อนอื่น Go เป็นภาษาประเภท static 100% คำว่า static สะท้อนอยู่ที่ประเภท interface แบบ abstract ที่แสดงออกภายนอกไม่เปลี่ยนแปลง ส่วน dynamic หมายถึงประเภท concrete ที่เก็บอยู่ด้านล่างของ interface สามารถเปลี่ยนแปลงได้ จนถึงตอนนี้ สำหรับหลักการง่ายๆ ของ interface只需要เข้าใจเพียงเท่านี้ก็เพียงพอสำหรับการเรียนรู้ reflection ในภายหลัง

สะพาน

ในแพ็กเกจ reflect มีประเภท interface reflect.Type สำหรับแสดงประเภทใน Go และประเภท struct reflect.Value สำหรับแสดงค่าใน Go

go
type Type interface {
    ...

    Name() string

  PkgPath() string

  Size() uintptr

  String() string

  Kind() Kind

    ...
}

type Value struct {

   typ *rtype

   ptr unsafe.Pointer

   flag

}

โค้ดข้างต้นละเว้นรายละเอียดมากมาย ก่อนอื่น只需要เข้าใจการมีอยู่ของสองประเภทนี้ การดำเนินการ reflection ทั้งหมดใน Go ขึ้นอยู่กับสองประเภทนี้ แพ็กเกจ reflect จัดเตรียมสองฟังก์ชันสำหรับแปลงประเภทใน Go เป็นสองประเภทข้างต้นเพื่อดำเนินการ reflection分别是 reflect.TypeOf ฟังก์ชัน

go
func TypeOf(i any) Type

และ reflect.ValueOf ฟังก์ชัน

go
func ValueOf(i any) Value

จะเห็นว่าประเภทพารามิเตอร์ของสองฟังก์ชันคือ any ซึ่งก็คือชื่อเล่นของ interface{} หากต้องการดำเนินการ reflection จำเป็นต้องแปลงประเภทเป็น interface{} ก่อน นี่คือเหตุผลที่กล่าวไว้ก่อนหน้านี้ว่า只要有 reflection ก็ต้องมี empty interface อย่างเคร่งครัดแล้ว empty interface คือสะพานเชื่อมระหว่างระบบประเภท Go กับ reflection ภาพด้านล่างอธิบายกระบวนการนี้อย่างมีชีวิตชีวา

TIP

ในเนื้อหาต่อไปนี้เพื่อความสะดวก จะใช้ชื่อเล่น any แทน interface{} อย่างสม่ำเสมอ

แกนหลัก

ใน Go มีกฎ reflection คลาสสิกสามข้อ เมื่อรวมกับเนื้อหาที่กล่าวมาข้างต้นก็จะเข้าใจได้ง่าย分别是

  1. Reflection สามารถแปลงตัวแปรประเภท interface{} เป็น object reflection

  2. Reflection สามารถแปลง object reflection กลับเป็นตัวแปรประเภท interface{}

  3. หากต้องการแก้ไข object reflection ค่าของมันต้องสามารถตั้งค่าได้

สามกฎนี้คือแกนหลักของ reflection ใน Go เมื่อต้องการเข้าถึงข้อมูลที่เกี่ยวข้องกับประเภท จำเป็นต้องใช้ reflect.TypeOf เมื่อต้องการแก้ไขค่า reflection จำเป็นต้องใช้ reflect.ValueOf

ประเภท

reflect.Type แสดงถึงประเภทใน Go ใช้ฟังก์ชัน reflect.TypeOf() สามารถแปลงตัวแปรเป็น reflect.Type ตัวอย่างโค้ดมีดังนี้

go
func main() {
  str := "hello world!"
  reflectType := reflect.TypeOf(str)
  fmt.Println(reflectType)
}

ผลลัพธ์ที่ส่งออกคือ

string

Kind

สำหรับ Type แล้ว Go ใช้ reflect.Kind ภายในเพื่อแสดงประเภทพื้นฐานใน Go โดยพื้นฐานแล้วคือ unsigned integer uint

go
type Kind uint

แพ็กเกจ reflect ใช้ Kind enumerate ประเภทพื้นฐานทั้งหมดใน Go ดังนี้

go
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
   Pointer
   Slice
   String
   Struct
   UnsafePointer
)

ประเภท Kind นำไปใช้เพียงวิธีการ String() ของ interface Stringer เท่านั้น ประเภทนี้มีเพียงวิธีการเดียวนี้เท่านั้น ค่าส่งกลับของวิธีการ String() มาจาก slice ภายในของมัน ดังนี้ การเขียนแบบนี้乍一看很像 map แต่จริงๆ แล้วนี่เป็นการเขียนพิเศษชนิดหนึ่งใน Go: ไวยากรณ์การเริ่มต้นด้วยดัชนี (index expressions in slice literals)

go
var kindNames = []string{
   Invalid:       "invalid",
   Bool:          "bool",
   Int:           "int",
   Int8:          "int8",
   Int16:         "int16",
   Int32:         "int32",
   Int64:         "int64",
   Uint:          "uint",
   Uint8:         "uint8",
   Uint16:        "uint16",
   Uint32:        "uint32",
   Uint64:        "uint64",
   Uintptr:       "uintptr",
   Float32:       "float32",
   Float64:       "float64",
   Complex64:     "complex64",
   Complex128:    "complex128",
   Array:         "array",
   Chan:          "chan",
   Func:          "func",
   Interface:     "interface",
   Map:           "map",
   Pointer:       "ptr",
   Slice:         "slice",
   String:        "string",
   Struct:        "struct",
   UnsafePointer: "unsafe.Pointer",
}
go
type Type interface{
    Kind() Kind
}

ผ่าน Kind สามารถทราบว่าค่าที่ empty interface เก็บอยู่คือประเภทพื้นฐานอะไร ตัวอย่างเช่น

go
func main() {
    // ประกาศตัวแปรประเภท any
  var eface any
    // กำหนดค่า
  eface = 100
    // ผ่านวิธีการ Kind เพื่อรับประเภทของมัน
  fmt.Println(reflect.TypeOf(eface).Kind())
}

ผลลัพธ์ที่ส่งออก

int

Elem

go
type Type interface{
    Elem() Type
}

ใช้วิธีการ Type.Elem() สามารถตัดสินประเภทองค์ประกอบที่เก็บอยู่ในโครงสร้างข้อมูลประเภท any ประเภทพารามิเตอร์พื้นฐานที่รับได้ต้องเป็นพอยน์เตอร์ สไลซ์ อาร์เรย์ แชนเนล map อย่างใดอย่างหนึ่ง มิฉะนั้นจะ panic ด้านล่างเป็นตัวอย่างโค้ด

go
func main() {
  var eface any
  eface = map[string]int{}
  rType := reflect.TypeOf(eface)
    // key() จะส่งคืนประเภท reflection ของ key map
  fmt.Println(rType.Key().Kind())
  fmt.Println(rType.Elem().Kind())
}

ผลลัพธ์คือ

string
int

พอยน์เตอร์ก็สามารถเข้าใจได้ว่าเป็น container หนึ่ง สำหรับพอยน์เตอร์ใช้ Elem() จะได้ประเภท reflection ขององค์ประกอบที่มันชี้ไป ตัวอย่างโค้ดมีดังนี้

go
func main() {
  var eface any
    // กำหนดค่าพอยน์เตอร์
  eface = new(strings.Builder)
  rType := reflect.TypeOf(eface)
    // รับประเภท reflection ขององค์ประกอบที่พอยน์เตอร์ชี้ไป
  vType := rType.Elem()
    // ส่งเสริมเส้นทางแพ็กเกจ
  fmt.Println(vType.PkgPath())
    // ส่งเสริมชื่อของมัน
  fmt.Println(vType.Name())
}
strings
Builder

สำหรับอาร์เรย์ สไลซ์ แชนเนล การใช้งานก็คล้ายกัน

Size

go
type Type interface{
    Size() uintptr
}

ผ่านวิธีการ Size สามารถรับขนาดバイトที่ประเภทนั้นครอบครอง ตัวอย่างมีดังนี้

go
func main() {
  fmt.Println(reflect.TypeOf(0).Size())
  fmt.Println(reflect.TypeOf("").Size())
  fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
  fmt.Println(reflect.TypeOf(0.1).Size())
  fmt.Println(reflect.TypeOf([]string{}).Size())
}

ผลลัพธ์ที่ส่งออกคือ

8
16
16
8
24

TIP

ใช้ unsafe.Sizeof() สามารถบรรลุผลเดียวกันได้

Comparable

go
type Type interface{
    Comparable() bool
}

ผ่านวิธีการ Comparable สามารถตัดสินว่าประเภทหนึ่งสามารถเปรียบเทียบได้หรือไม่ ตัวอย่างมีดังนี้

go
func main() {
  fmt.Println(reflect.TypeOf("hello world!").Comparable())
  fmt.Println(reflect.TypeOf(1024).Comparable())
  fmt.Println(reflect.TypeOf([]int{}).Comparable())
  fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}

ผลลัพธ์ดังนี้

true
true
false
true

Implements

go
type Type interface{
    Implements(u Type) bool
}

ผ่านวิธีการ Implements สามารถตัดสินว่าประเภทหนึ่งนำไปใช้ interface หนึ่งหรือไม่

go
type MyInterface interface {
  My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
  return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
  return "his"
}

func main() {
  rIface := reflect.TypeOf(new(MyInterface)).Elem()
  fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
  fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}

ผลลัพธ์ที่ส่งออก

true
false

ConvertibleTo

go
type Type interface{
    ConvertibleTo(u Type) bool
}

ใช้วิธีการ ConvertibleTo สามารถตัดสินว่าประเภทหนึ่งสามารถแปลงเป็นอีกประเภทที่ระบุได้หรือไม่

go
type MyInterface interface {
  My() string
}

type MyStruct struct {
}

func (m MyStruct) My() string {
  return "my"
}

type HisStruct struct {
}

func (h HisStruct) String() string {
  return "his"
}

func main() {
  rIface := reflect.TypeOf(new(MyInterface)).Elem()
  fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
  fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}

ผลลัพธ์

true
false

ค่า

reflect.Value แสดงถึงค่าของ interface reflection ใช้ฟังก์ชัน reflect.ValueOf() สามารถแปลงตัวแปรเป็น reflect.Value ตัวอย่างโค้ดมีดังนี้

go
func main() {
  str := "hello world!"
  reflectValue := reflect.ValueOf(str)
  fmt.Println(reflectValue)
}

ผลลัพธ์ที่ส่งออกคือ

hello world!

Type

go
func (v Value) Type() Type

วิธีการ Type สามารถรับประเภทของค่า reflection

go
func main() {
   num := 114514
   rValue := reflect.ValueOf(num)
   fmt.Println(rValue.Type())
}

ผลลัพธ์

int

Elem

go
func (v Value) Elem() Value

รับค่า reflection ขององค์ประกอบของค่า reflection

go
func main() {
   num := new(int)
   *num = 114514
   // ใช้พอยน์เตอร์เป็นตัวอย่าง
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

ผลลัพธ์

114514

พอยน์เตอร์

การรับพอยน์เตอร์ของค่า reflection มีสองวิธี

go
// ส่งคืนค่า reflection ของพอยน์เตอร์ที่แสดงถึงที่อยู่ของ v
func (v Value) Addr() Value

// ส่งคืน uinptr ที่ชี้ไปยังค่าเดิมของ v เทียบเท่ากับ uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr

// ส่งคืน uintptr ที่ชี้ไปยังค่าเดิมของ v
// เฉพาะเมื่อ Kind ของ v เป็น Chan, Func, Map, Pointer, Slice, UnsafePointer มิฉะนั้นจะ panic
func (v Value) Pointer() uintptr

// ส่งคืน unsafe.Pointer ที่ชี้ไปยังค่าเดิมของ v
// เฉพาะเมื่อ Kind ของ v เป็น Chan, Func, Map, Pointer, Slice, UnsafePointer มิฉะนั้นจะ panic
func (v Value) UnsafePointer() unsafe.Pointer

ตัวอย่างมีดังนี้

go
func main() {
   num := 1024
   ele := reflect.ValueOf(&num).Elem()
   fmt.Println("&num", &num)
   fmt.Println("Addr", ele.Addr())
   fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
   fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
   fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}

ผลลัพธ์

&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058

TIP

fmt.Println จะ reflection รับประเภทของพารามิเตอร์ หากเป็นประเภท reflect.Value จะเรียก Value.Interface() โดยอัตโนมัติเพื่อรับค่าเดิมของมัน

เปลี่ยนเป็น map แล้วทำอีกครั้ง

go
func main() {
  dic := map[string]int{}
  ele := reflect.ValueOf(&dic).Elem()
  println(dic)
  fmt.Println("Addr", ele.Addr())
  fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
  fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
  fmt.Println("UnsafePointer", ele.UnsafePointer())
}

ผลลัพธ์

0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0

ตั้งค่า

go
func (v Value) Set(x Value)

หากต้องการแก้ไขค่า reflection ผ่าน reflection แล้ว ค่าของมันต้องสามารถอ้างอิงได้ ในเวลานี้ควรแก้ไขค่าองค์ประกอบผ่านพอยน์เตอร์ แทนที่จะพยายามแก้ไขค่าขององค์ประกอบโดยตรง

go
func main() {
   // *int
   num := new(int)
   *num = 114514
   rValue := reflect.ValueOf(num)
    // รับองค์ประกอบที่พอยน์เตอร์ชี้ไป
   ele := rValue.Elem()
   fmt.Println(ele.Interface())
   ele.SetInt(11)
   fmt.Println(ele.Interface())
}

ผลลัพธ์ดังนี้

114514
11

รับค่า

go
func (v Value) Interface() (i any)

ผ่านวิธีการ Interface() สามารถรับค่าเดิมของค่า reflection

go
func main() {
   var str string
   str = "hello"
   rValue := reflect.ValueOf(str)
   if v, ok := rValue.Interface().(string); ok {
      fmt.Println(v)
   }
}

ผลลัพธ์

hello

ฟังก์ชัน

ผ่าน reflection สามารถรับข้อมูลทั้งหมดของฟังก์ชันได้ และสามารถเรียกฟังก์ชันผ่าน reflection ได้

ข้อมูล

รับข้อมูลทั้งหมดของฟังก์ชันผ่านประเภท reflection

go
func Max(a, b int) int {
   if a > b {
      return a
   }
   return b
}

func main() {
   rType := reflect.TypeOf(Max)
   // ส่งเสริมชื่อฟังก์ชัน ประเภทฟังก์ชัน literal ไม่มีชื่อ
   fmt.Println(rType.Name())
   // ส่งเสริมจำนวนพารามิเตอร์ ค่าส่งกลับ
   fmt.Println(rType.NumIn(), rType.NumOut())
   rParamType := rType.In(0)
   // ส่งเสริมประเภทของพารามิเตอร์แรก
   fmt.Println(rParamType.Kind())
   rResType := rType.Out(0)
   // ส่งเสริมประเภทของค่าส่งกลับแรก
   fmt.Println(rResType.Kind())
}

ผลลัพธ์


2 1
int
int

เรียก

เรียกฟังก์ชันผ่านค่า reflection

go
func (v Value) Call(in []Value) []Value
go
func main() {
   // รับค่า reflection ของฟังก์ชัน
   rType := reflect.ValueOf(Max)
   // ส่งอาร์เรย์พารามิเตอร์
   rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
   for _, value := range rResValue {
      fmt.Println(value.Interface())
   }
}

ผลลัพธ์

50

Struct

สมมติว่ามี struct ดังนี้

go
type Person struct {
  Name    string `json:"name"`
  Age     int    `json:"age"`
  Address string `json:"address"`
  money   int
}

func (p Person) Talk(msg string) string {
  return msg
}

เข้าถึงฟิลด์

โครงสร้างของ reflect.StructField มีดังนี้

go
type StructField struct {
  // ชื่อฟิลด์
  Name string
  // ชื่อแพ็กเกจ
  PkgPath string
  // ชื่อประเภท
  Type      Type
  // Tag
  Tag       StructTag
  // การเยื้องバイトของฟิลด์
  Offset    uintptr
  // ดัชนี
  Index     []int
  // เป็นฟิลด์ embedded หรือไม่
  Anonymous bool
}

วิธีการเข้าถึงฟิลด์ของ struct มีสองวิธี วิธีหนึ่งคือเข้าถึงผ่านดัชนี อีกวิธีหนึ่งคือเข้าถึงผ่านชื่อ

go
type Type interface{
    Field(i int) StructField
}

ตัวอย่างการเข้าถึงผ่านดัชนีมีดังนี้

go
func main() {
  rType := reflect.TypeOf(new(Person)).Elem()
  // ส่งเสริมจำนวนฟิลด์ struct
  fmt.Println(rType.NumField())
  for i := 0; i < rType.NumField(); i++ {
    structField := rType.Field(i)
    fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
  }
}

ผลลัพธ์

4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 false
go
type Type interface{
    FieldByName(name string) (StructField, bool)
}

ตัวอย่างการเข้าถึงผ่านชื่อมีดังนี้

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   // ส่งเสริมจำนวนฟิลด์ struct
   fmt.Println(rType.NumField())
   if field, ok := rType.FieldByName("money"); ok {
      fmt.Println(field.Name, field.Type, field.IsExported())
   }
}

ผลลัพธ์

4
money int false

แก้ไขฟิลด์

หากต้องการแก้ไขค่าฟิลด์ struct จำเป็นต้องส่งพอยน์เตอร์ struct ด้านล่างเป็นตัวอย่างการแก้ไขฟิลด์

go
func main() {
  // ส่งพอยน์เตอร์
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // รับฟิลด์
  name := rValue.FieldByName("Name")
  // แก้ไขค่าฟิลด์
  if (name != reflect.Value{}) { // หากส่งคืน reflect.Value{} แสดงว่าฟิลด์นั้นไม่มีอยู่
    name.SetString("jack")
  }
  // ส่งเสริม struct
  fmt.Println(rValue.Interface())
}

ผลลัพธ์

{jack 0  0}

สำหรับการแก้ไขฟิลด์ private ของ struct จำเป็นต้องดำเนินการเพิ่มเติมบางประการ ดังนี้

go
func main() {
  // ส่งพอยน์เตอร์
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // รับฟิลด์ private หนึ่งฟิลด์
  money := rValue.FieldByName("money")
  // แก้ไขค่าฟิลด์
  if (money != reflect.Value{}) {
    // สร้างค่า reflection ของพอยน์เตอร์ที่ชี้ไปยังฟิลด์ที่ไม่ได้ส่งออกของ struct นั้น
    p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
    // รับองค์ประกอบที่พอยน์เตอร์นั้นชี้ไป ซึ่งก็คือฟิลด์ที่ต้องการแก้ไข
    field := p.Elem()
    // แก้ไขค่า
    field.SetInt(164)
  }
  // ส่งเสริม struct
  fmt.Printf("%+v\n", rValue.Interface())
}

เข้าถึง Tag

หลังจากได้รับ StructField แล้ว สามารถเข้าถึง Tag ของมันได้โดยตรง

go
// หากไม่มีอยู่ ok เป็น false
func (tag StructTag) Lookup(key string) (value string, ok bool)

// หากไม่มีอยู่ ส่งคืนสตริงว่าง
func (tag StructTag) Get(key string) string

ตัวอย่างมีดังนี้

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   name, ok := rType.FieldByName("Name")
   if ok {
      fmt.Println(name.Tag.Lookup("json"))
      fmt.Println(name.Tag.Get("json"))
   }
}

ผลลัพธ์

name true
name

เข้าถึงเมธอด

การเข้าถึงเมธอดคล้ายกับกระบวนการเข้าถึงฟิลด์ เพียงแต่ลายเซ็นฟังก์ชันแตกต่างกันเล็กน้อย โครงสร้าง reflect.Method มีดังนี้

go
type Method struct {
  // ชื่อเมธอด
  Name string
  // ชื่อแพ็กเกจ
  PkgPath string
  // ประเภทเมธอด
  Type  Type
  // ฟังก์ชันที่สอดคล้องกับเมธอด พารามิเตอร์แรกคือ receiver
  Func  Value
  // ดัชนี
  Index int
}

ตัวอย่างการเข้าถึงข้อมูลเมธอดมีดังนี้

go
func main() {
  // รับประเภท reflection ของ struct
  rType := reflect.TypeOf(new(Person)).Elem()
  // ส่งเสริมจำนวนเมธอด
  fmt.Println(rType.NumMethod())
  // 遍历ส่งเสริมข้อมูลเมธอด
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
  }
}

ผลลัพธ์

1
0 Talk func(main.Person, string) string true

หากต้องการรับรายละเอียดพารามิเตอร์และค่าส่งกลับของเมธอด สามารถรับผ่าน Method.Func ได้ กระบวนการเหมือนกับการเข้าถึงข้อมูลฟังก์ชัน แก้ไขโค้ดข้างต้นเล็กน้อย

go
func main() {
  // รับประเภท reflection ของ struct
  rType := reflect.TypeOf(new(Person)).Elem()
  // ส่งเสริมจำนวนเมธอด
  fmt.Println(rType.NumMethod())
  // 遍历ส่งเสริมข้อมูลเมธอด
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
    fmt.Println("พารามิเตอร์เมธอด")
    for i := 0; i < method.Func.Type().NumIn(); i++ {
      fmt.Println(method.Func.Type().In(i).String())
    }
    fmt.Println("ค่าส่งกลับเมธอด")
    for i := 0; i < method.Func.Type().NumOut(); i++ {
      fmt.Println(method.Func.Type().Out(i).String())
    }
  }
}

จะเห็นว่าพารามิเตอร์แรกคือ main.Person ซึ่งก็คือประเภท receiver

1
0 Talk func(main.Person, string) string true
พารามิเตอร์เมธอด
main.Person
string
ค่าส่งกลับเมธอด
string

เรียกเมธอด

การเรียกเมธอดคล้ายกับกระบวนการเรียกฟังก์ชัน และไม่จำเป็นต้องส่ง receiver ด้วยตนเอง ตัวอย่างมีดังนี้

go
func main() {
   // รับประเภท reflection ของ struct
   rValue := reflect.ValueOf(new(Person)).Elem()
   // ส่งเสริมจำนวนเมธอด
   fmt.Println(rValue.NumMethod())
   // 遍历ส่งเสริมข้อมูลเมธอด
   talk := rValue.MethodByName("Talk")
   if (talk != reflect.Value{}) {
      // เรียกเมธอด และรับค่าส่งกลับ
      res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
      // 遍历ส่งเสริมค่าส่งกลับ
      for _, re := range res {
         fmt.Println(re.Interface())
      }
   }
}

ผลลัพธ์

1
hello,reflect!

สร้าง

ผ่าน reflection สามารถสร้างค่าใหม่ได้ แพ็กเกจ reflect จัดเตรียมฟังก์ชันที่สะดวกกว่าสำหรับประเภทพิเศษบางประเภท

ประเภทพื้นฐาน

go
// ส่งคืนค่า reflection ของพอยน์เตอร์ที่ชี้ไปยังค่า reflection
func New(typ Type) Value

ใช้ string เป็นตัวอย่าง

go
func main() {
   rValue := reflect.New(reflect.TypeOf(*new(string)))
   rValue.Elem().SetString("hello world!")
   fmt.Println(rValue.Elem().Interface())
}
hello world!

Struct

การสร้าง struct ก็ใช้ฟังก์ชัน reflect.New เช่นกัน

go
type Person struct {
   Name    string `json:"name"`
   Age     int    `json:"age"`
   Address string `json:"address"`
   money   int
}

func (p Person) Talk(msg string) string {
   return msg
}

func main() {
   // สร้างค่า reflection ของ struct
   rType := reflect.TypeOf(new(Person)).Elem()
   person := reflect.New(rType).Elem()
   fmt.Println(person.Interface())
}

ผลลัพธ์

{ 0  0}

สไลซ์

สร้างสไลซ์ผ่าน reflection

go
func MakeSlice(typ Type, len, cap int) Value
go
func main() {
   // สร้างค่า reflection ของสไลซ์
   rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
   // 遍历กำหนดค่า
   for i := 0; i < 10; i++ {
      rValue.Index(i).SetInt(int64(i))
   }
   fmt.Println(rValue.Interface())
}
[0 1 2 3 4 5 6 7 8 9]

Map

สร้าง Map ผ่าน reflection

go
func MakeMapWithSize(typ Type, n int) Value
go
func main() {
   // สร้างค่า reflection ของ map
   rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
   // กำหนดค่า
   rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
   fmt.Println(rValue.Interface())
}
map[a:1]

แชนเนล

สร้างแชนเนลผ่าน reflection

go
func MakeChan(typ Type, buffer int) Value
go
func main() {
   // สร้างค่า reflection ของแชนเนล
   makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
   fmt.Println(makeChan.Interface())
}

ฟังก์ชัน

สร้างฟังก์ชันผ่าน reflection

go
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
go
func main() {
    // ส่งประเภท wrapper และ body ของฟังก์ชัน
  fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
    for _, arg := range args {
      fmt.Println(arg.Interface())
    }
    return nil
  })
  fmt.Println(fn.Type())
  fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}

ผลลัพธ์

func(int)
1024

เท่ากันโดยสมบูรณ์

reflect.DeepEqual เป็นฟังก์ชันที่แพ็กเกจ reflection จัดเตรียมไว้สำหรับตัดสินว่าตัวแปรสองตัวเท่ากันโดยสมบูรณ์หรือไม่ ลายเซ็นมีดังนี้

go
func DeepEqual(x, y any) bool

ฟังก์ชันนี้จัดการกับแต่ละประเภทพื้นฐาน ด้านล่างเป็นวิธีการตัดสินบางประเภท

  • อาร์เรย์: ทุกองค์ประกอบในอาร์เรย์เท่ากันโดยสมบูรณ์
  • สไลซ์: เมื่อเป็น nil ทั้งคู่ตัดสินว่าเท่ากันโดยสมบูรณ์ หรือเมื่อไม่ว่างทั้งคู่ องค์ประกอบในช่วงความยาวเท่ากันโดยสมบูรณ์
  • Struct: ฟิลด์ทั้งหมดเท่ากันโดยสมบูรณ์
  • Map: เมื่อเป็น nil ทั้งคู่เป็นเท่ากันโดยสมบูรณ์ เมื่อไม่เป็น nil ทั้งคู่ ค่าที่映射โดยทุก key เท่ากันโดยสมบูรณ์
  • พอยน์เตอร์: ชี้ไปยังองค์ประกอบเดียวกันหรือองค์ประกอบที่ชี้ไปเท่ากันโดยสมบูรณ์
  • Interface: เมื่อประเภท concrete ของ interface เท่ากันโดยสมบูรณ์
  • ฟังก์ชัน: เท่ากันโดยสมบูรณ์ก็ต่อเมื่อทั้งคู่เป็น nil มิฉะนั้นไม่เท่ากันโดยสมบูรณ์

ด้านล่างเป็นบางตัวอย่าง:

สไลซ์

go
func main() {
   a := make([]int, 100)
   b := make([]int, 100)
   fmt.Println(reflect.DeepEqual(a, b))
}

ผลลัพธ์

true

Struct

go
func main() {
   mike := Person{
      Name:   "mike",
      Age:    39,
      Father: nil,
   }

   jack := Person{
      Name:   "jack",
      Age:    18,
      Father: &mike,
   }

   tom := Person{
      Name:   "tom",
      Age:    18,
      Father: &mike,
   }
   fmt.Println(reflect.DeepEqual(mike, jack))
   fmt.Println(reflect.DeepEqual(tom, jack))
   fmt.Println(reflect.DeepEqual(jack, jack))
}

ผลลัพธ์

false
false
true

Golang by www.golangdev.cn edit