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 ดังนี้
type iface struct {
tab *itab // ประกอบด้วย ประเภทข้อมูล, ประเภท interface, method set ฯลฯ
data unsafe.Pointer // พอยน์เตอร์ที่ชี้ไปยังค่า
}สำหรับ interface ที่ไม่มี method set นั้น ขณะรันไทม์จะถูกแสดงโดย struct eface ดังนี้
type eface struct {
_type *_type // ประเภท
data unsafe.Pointer // พอยน์เตอร์ที่ชี้ไปยังค่า
}และสอง struct นี้ในแพ็กเกจ reflect มีประเภท struct ที่สอดคล้องกัน iface สอดคล้องกับ nonEmptyInterface
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
type emptyInterface struct {
typ *rtype // ประเภท concrete แบบ dynamic
word unsafe.Pointer // ค่าที่ชี้ไปยังพอยน์เตอร์
}สำหรับสองประเภทนี้ ทางทางการได้ให้นิยามที่ชัดเจนมาก
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface: 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
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 ฟังก์ชัน
func TypeOf(i any) Typeและ reflect.ValueOf ฟังก์ชัน
func ValueOf(i any) Valueจะเห็นว่าประเภทพารามิเตอร์ของสองฟังก์ชันคือ any ซึ่งก็คือชื่อเล่นของ interface{} หากต้องการดำเนินการ reflection จำเป็นต้องแปลงประเภทเป็น interface{} ก่อน นี่คือเหตุผลที่กล่าวไว้ก่อนหน้านี้ว่า只要有 reflection ก็ต้องมี empty interface อย่างเคร่งครัดแล้ว empty interface คือสะพานเชื่อมระหว่างระบบประเภท Go กับ reflection ภาพด้านล่างอธิบายกระบวนการนี้อย่างมีชีวิตชีวา

TIP
ในเนื้อหาต่อไปนี้เพื่อความสะดวก จะใช้ชื่อเล่น any แทน interface{} อย่างสม่ำเสมอ
แกนหลัก
ใน Go มีกฎ reflection คลาสสิกสามข้อ เมื่อรวมกับเนื้อหาที่กล่าวมาข้างต้นก็จะเข้าใจได้ง่าย分别是
Reflection สามารถแปลงตัวแปรประเภท
interface{}เป็น object reflectionReflection สามารถแปลง object reflection กลับเป็นตัวแปรประเภท
interface{}หากต้องการแก้ไข object reflection ค่าของมันต้องสามารถตั้งค่าได้
สามกฎนี้คือแกนหลักของ reflection ใน Go เมื่อต้องการเข้าถึงข้อมูลที่เกี่ยวข้องกับประเภท จำเป็นต้องใช้ reflect.TypeOf เมื่อต้องการแก้ไขค่า reflection จำเป็นต้องใช้ reflect.ValueOf
ประเภท
reflect.Type แสดงถึงประเภทใน Go ใช้ฟังก์ชัน reflect.TypeOf() สามารถแปลงตัวแปรเป็น reflect.Type ตัวอย่างโค้ดมีดังนี้
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}ผลลัพธ์ที่ส่งออกคือ
stringKind
สำหรับ Type แล้ว Go ใช้ reflect.Kind ภายในเพื่อแสดงประเภทพื้นฐานใน Go โดยพื้นฐานแล้วคือ unsigned integer uint
type Kind uintแพ็กเกจ reflect ใช้ Kind enumerate ประเภทพื้นฐานทั้งหมดใน 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)
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",
}type Type interface{
Kind() Kind
}ผ่าน Kind สามารถทราบว่าค่าที่ empty interface เก็บอยู่คือประเภทพื้นฐานอะไร ตัวอย่างเช่น
func main() {
// ประกาศตัวแปรประเภท any
var eface any
// กำหนดค่า
eface = 100
// ผ่านวิธีการ Kind เพื่อรับประเภทของมัน
fmt.Println(reflect.TypeOf(eface).Kind())
}ผลลัพธ์ที่ส่งออก
intElem
type Type interface{
Elem() Type
}ใช้วิธีการ Type.Elem() สามารถตัดสินประเภทองค์ประกอบที่เก็บอยู่ในโครงสร้างข้อมูลประเภท any ประเภทพารามิเตอร์พื้นฐานที่รับได้ต้องเป็นพอยน์เตอร์ สไลซ์ อาร์เรย์ แชนเนล map อย่างใดอย่างหนึ่ง มิฉะนั้นจะ panic ด้านล่างเป็นตัวอย่างโค้ด
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 ขององค์ประกอบที่มันชี้ไป ตัวอย่างโค้ดมีดังนี้
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
type Type interface{
Size() uintptr
}ผ่านวิธีการ Size สามารถรับขนาดバイトที่ประเภทนั้นครอบครอง ตัวอย่างมีดังนี้
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
24TIP
ใช้ unsafe.Sizeof() สามารถบรรลุผลเดียวกันได้
Comparable
type Type interface{
Comparable() bool
}ผ่านวิธีการ Comparable สามารถตัดสินว่าประเภทหนึ่งสามารถเปรียบเทียบได้หรือไม่ ตัวอย่างมีดังนี้
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
trueImplements
type Type interface{
Implements(u Type) bool
}ผ่านวิธีการ Implements สามารถตัดสินว่าประเภทหนึ่งนำไปใช้ interface หนึ่งหรือไม่
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
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}ใช้วิธีการ ConvertibleTo สามารถตัดสินว่าประเภทหนึ่งสามารถแปลงเป็นอีกประเภทที่ระบุได้หรือไม่
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 ตัวอย่างโค้ดมีดังนี้
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}ผลลัพธ์ที่ส่งออกคือ
hello world!Type
func (v Value) Type() Typeวิธีการ Type สามารถรับประเภทของค่า reflection
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}ผลลัพธ์
intElem
func (v Value) Elem() Valueรับค่า reflection ขององค์ประกอบของค่า reflection
func main() {
num := new(int)
*num = 114514
// ใช้พอยน์เตอร์เป็นตัวอย่าง
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}ผลลัพธ์
114514พอยน์เตอร์
การรับพอยน์เตอร์ของค่า reflection มีสองวิธี
// ส่งคืนค่า 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ตัวอย่างมีดังนี้
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 0xc0000a6058TIP
fmt.Println จะ reflection รับประเภทของพารามิเตอร์ หากเป็นประเภท reflect.Value จะเรียก Value.Interface() โดยอัตโนมัติเพื่อรับค่าเดิมของมัน
เปลี่ยนเป็น map แล้วทำอีกครั้ง
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ตั้งค่า
func (v Value) Set(x Value)หากต้องการแก้ไขค่า reflection ผ่าน reflection แล้ว ค่าของมันต้องสามารถอ้างอิงได้ ในเวลานี้ควรแก้ไขค่าองค์ประกอบผ่านพอยน์เตอร์ แทนที่จะพยายามแก้ไขค่าขององค์ประกอบโดยตรง
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รับค่า
func (v Value) Interface() (i any)ผ่านวิธีการ Interface() สามารถรับค่าเดิมของค่า reflection
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
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
func (v Value) Call(in []Value) []Valuefunc 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())
}
}ผลลัพธ์
50Struct
สมมติว่ามี struct ดังนี้
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 มีดังนี้
type StructField struct {
// ชื่อฟิลด์
Name string
// ชื่อแพ็กเกจ
PkgPath string
// ชื่อประเภท
Type Type
// Tag
Tag StructTag
// การเยื้องバイトของฟิลด์
Offset uintptr
// ดัชนี
Index []int
// เป็นฟิลด์ embedded หรือไม่
Anonymous bool
}วิธีการเข้าถึงฟิลด์ของ struct มีสองวิธี วิธีหนึ่งคือเข้าถึงผ่านดัชนี อีกวิธีหนึ่งคือเข้าถึงผ่านชื่อ
type Type interface{
Field(i int) StructField
}ตัวอย่างการเข้าถึงผ่านดัชนีมีดังนี้
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 falsetype Type interface{
FieldByName(name string) (StructField, bool)
}ตัวอย่างการเข้าถึงผ่านชื่อมีดังนี้
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 ด้านล่างเป็นตัวอย่างการแก้ไขฟิลด์
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 จำเป็นต้องดำเนินการเพิ่มเติมบางประการ ดังนี้
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 ของมันได้โดยตรง
// หากไม่มีอยู่ ok เป็น false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// หากไม่มีอยู่ ส่งคืนสตริงว่าง
func (tag StructTag) Get(key string) stringตัวอย่างมีดังนี้
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 มีดังนี้
type Method struct {
// ชื่อเมธอด
Name string
// ชื่อแพ็กเกจ
PkgPath string
// ประเภทเมธอด
Type Type
// ฟังก์ชันที่สอดคล้องกับเมธอด พารามิเตอร์แรกคือ receiver
Func Value
// ดัชนี
Index int
}ตัวอย่างการเข้าถึงข้อมูลเมธอดมีดังนี้
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 ได้ กระบวนการเหมือนกับการเข้าถึงข้อมูลฟังก์ชัน แก้ไขโค้ดข้างต้นเล็กน้อย
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 ด้วยตนเอง ตัวอย่างมีดังนี้
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 จัดเตรียมฟังก์ชันที่สะดวกกว่าสำหรับประเภทพิเศษบางประเภท
ประเภทพื้นฐาน
// ส่งคืนค่า reflection ของพอยน์เตอร์ที่ชี้ไปยังค่า reflection
func New(typ Type) Valueใช้ string เป็นตัวอย่าง
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 เช่นกัน
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
func MakeSlice(typ Type, len, cap int) Valuefunc 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
func MakeMapWithSize(typ Type, n int) Valuefunc 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
func MakeChan(typ Type, buffer int) Valuefunc main() {
// สร้างค่า reflection ของแชนเนล
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}ฟังก์ชัน
สร้างฟังก์ชันผ่าน reflection
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc 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 จัดเตรียมไว้สำหรับตัดสินว่าตัวแปรสองตัวเท่ากันโดยสมบูรณ์หรือไม่ ลายเซ็นมีดังนี้
func DeepEqual(x, y any) boolฟังก์ชันนี้จัดการกับแต่ละประเภทพื้นฐาน ด้านล่างเป็นวิธีการตัดสินบางประเภท
- อาร์เรย์: ทุกองค์ประกอบในอาร์เรย์เท่ากันโดยสมบูรณ์
- สไลซ์: เมื่อเป็น
nilทั้งคู่ตัดสินว่าเท่ากันโดยสมบูรณ์ หรือเมื่อไม่ว่างทั้งคู่ องค์ประกอบในช่วงความยาวเท่ากันโดยสมบูรณ์ - Struct: ฟิลด์ทั้งหมดเท่ากันโดยสมบูรณ์
- Map: เมื่อเป็น
nilทั้งคู่เป็นเท่ากันโดยสมบูรณ์ เมื่อไม่เป็นnilทั้งคู่ ค่าที่映射โดยทุก key เท่ากันโดยสมบูรณ์ - พอยน์เตอร์: ชี้ไปยังองค์ประกอบเดียวกันหรือองค์ประกอบที่ชี้ไปเท่ากันโดยสมบูรณ์
- Interface: เมื่อประเภท concrete ของ interface เท่ากันโดยสมบูรณ์
- ฟังก์ชัน: เท่ากันโดยสมบูรณ์ก็ต่อเมื่อทั้งคู่เป็น
nilมิฉะนั้นไม่เท่ากันโดยสมบูรณ์
ด้านล่างเป็นบางตัวอย่าง:
สไลซ์
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}ผลลัพธ์
trueStruct
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