Skip to content

الانعكاس

الانعكاس هو آلية لفحص بنية اللغة نفسها في وقت التشغيل، ويمكنها التعامل بمرونة مع بعض المشاكل، لكن في نفس الوقت العيوب واضحة جدًا، مثل مشاكل الأداء وغيرها. في Go، الانعكاس مرتبط ارتباطًا وثيقًا بـ interface{}، وبشكل كبير، أينما يظهر interface{}، يكون هناك انعكاس. واجهة برمجة الانعكاس API في Go توفرها الحزمة القياسية reflect.

الواجهة

قبل البدء، دعنا نفهم باختصار الواجهتين الموجودتين في حزمة runtime. في Go، الواجهات هي في الأساس هياكل (struct)، ويقسم Go الواجهات في وقت التشغيل إلى فئتين كبيرتين: واحدة هي واجهات بدون مجموعة دوال، والأخرى هي واجهات بمجموعة دوال. بالنسبة للواجهات التي تحتوي على مجموعة دوال، يتم تمثيلها في وقت التشغيل بالهيكل iface التالي:

go
type iface struct {
   tab  *itab // يحتوي على نوع البيانات، نوع الواجهة، مجموعة الدوال وغيرها
   data unsafe.Pointer // مؤشر للقيمة
}

أما بالنسبة للواجهات بدون مجموعة دوال، يتم تمثيلها في وقت التشغيل بالهيكل eface التالي:

go
type eface struct {
   _type *_type // النوع
   data  unsafe.Pointer // مؤشر للقيمة
}

وكلا الهيكلين لهما هيكلان مقابلان في حزمة reflect، iface يقابله nonEmptyInterface:

go
type nonEmptyInterface struct {
  itab *struct {
    ityp *rtype // نوع الواجهة الثابت
    typ  *rtype // النوع المحدد الديناميكي
    hash uint32 // تجزئة النوع
    _    [4]byte
    fun  [100000]unsafe.Pointer // مجموعة الدوال
  }
  word unsafe.Pointer // مؤشر للقيمة
}

وeface يقابله emptyInterface:

go
type emptyInterface struct {
   typ  *rtype // النوع المحدد الديناميكي
   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. أولاً، Go لغة ثابتة النوع 100%، والثبات يتجلى في أن نوع الواجهة المجردة الذي يظهر خارجيًا لا يتغير، بينما الديناميكية تعني أن النوع المحدد المخزن أسفل الواجهة يمكن أن يتغير. إلى هنا، فهم المبادئ البسيطة للواجهات يكفي لتعلم الانعكاس لاحقًا.

الجسر

في حزمة reflect، هناك نوع واجهة reflect.Type لتمثيل الأنواع في Go، ونوع هيكل 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

}

الكود أعلاه حذف الكثير من التفاصيل، فقط نحتاج لفهم وجود هذين النوعين. جميع العمليات المتعلقة بالانعكاس في Go مبنية على هذين النوعين. توفر حزمة reflect دالتين لتحويل الأنواع في Go إلى النوعين المذكورين لإجراء عمليات الانعكاس، وهما دالة reflect.TypeOf:

go
func TypeOf(i any) Type

ودالة reflect.ValueOf:

go
func ValueOf(i any) Value

يمكن ملاحظة أن معاملتي الدالتين من النوع any، وهو الاسم المستعار لـ interface{}. إذا أردنا إجراء عمليات انعكاس، نحتاج أولاً لتحويل النوع إلى interface{}، ولهذا ذكرنا سابقًا أن وجود الانعكاس يعني وجود الواجهة الفارغة. بشكل غير دقيق، الواجهة الفارغة هي الجسر الذي يربط نظام أنواع Go بالانعكاس، والصورة التالية تصف هذه العملية بشكل جيد.

TIP

فيما يلي، للتبسيط، سنستخدم الاسم المستعار any بدلاً من interface{}

الأساسيات

في Go، هناك ثلاثة قوانين كلاسيكية للانعكاس، وبالاقتران مع المحتوى المذكور أعلاه، من السهل جدًا فهمها، وهي:

  1. الانعكاس يمكنه تحويل متغير من نوع interface{} إلى كائن انعكاس

  2. الانعكاس يمكنه استعادة كائن الانعكاس إلى متغير من نوع interface{}

  3. لتعديل كائن الانعكاس، يجب أن تكون قيمته قابلة للإعداد

هذه القوانين الثلاثة هي أساس انعكاس Go. عندما نحتاج للوصول إلى المعلومات المتعلقة بالنوع، نحتاج لاستخدام reflect.TypeOf، وعندما نحتاج لتعديل قيمة الانعكاس، نحتاج لاستخدام 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، وهو في الأساس نوع صحيح بدون إشارة uint.

go
type Kind uint

حزمة reflect تستخدم Kind لتعداد جميع الأنواع الأساسية في 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() لواجهة 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، يمكننا معرفة ما هو النوع الأساسي للقيمة المخزنة في الواجهة الفارغة، على سبيل المثال:

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، يجب أن يكون نوع المعامل الأساسي واحدًا من: مؤشر، شريحة، مصفوفة، قناة، أو خريطة، وإلا سيحدث panic. مثال الكود التالي:

go
func main() {
  var eface any
  eface = map[string]int{}
  rType := reflect.TypeOf(eface)
    // Key() سيعيد نوع انعكاس مفتاح الخريطة
  fmt.Println(rType.Key().Kind())
  fmt.Println(rType.Elem().Kind())
}

الإخراج:

string
int

المؤشر يمكن فهمه أيضًا كحاوية، واستخدام Elem() على المؤشر سيعطي نوع انعكاس العنصر الذي يشير إليه، مثال الكود:

go
func main() {
  var eface any
    // إسناد مؤشر
  eface = new(strings.Builder)
  rType := reflect.TypeOf(eface)
    // الحصول على نوع انعكاس العنصر الذي يشير إليه المؤشر
  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 يمكن تحديد ما إذا كان النوع يطبق واجهة معينة:

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 تمثل قيمة واجهة الانعكاس، واستخدام دالة 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 يمكنها الحصول على نوع قيمة الانعكاس:

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

الإخراج:

int

Elem

go
func (v Value) Elem() Value

الحصول على قيمة انعكاس العنصر لقيمة انعكاس:

go
func main() {
   num := new(int)
   *num = 114514
   // مثال بالمؤشر
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

الإخراج:

114514

المؤشر

هناك طريقتان للحصول على مؤشر قيمة الانعكاس:

go
// تعيد قيمة انعكاس للمؤشر الذي يمثل عنوان v
func (v Value) Addr() Value

// تعيد uintptr لعنوان القيمة الأصلية لـ 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 سيأخذ معامل النوع بالانعكاس، إذا كان من نوع reflect.Value، سيستدعي تلقائيًا Value.Interface() للحصول على قيمته الأصلية.

لنجرب مرة أخرى مع خريطة:

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)

إذا أردنا تعديل قيمة الانعكاس من خلال الانعكاس، فيجب أن تكون القيمة قابلة للعنونة، وفي هذه الحالة يجب تعديل قيمة العنصر من خلال المؤشر، بدلاً من محاولة تعديل قيمة العنصر مباشرة.

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() يمكن الحصول على القيمة الأصلية لقيمة الانعكاس:

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

الإخراج:

hello

الدوال

من خلال الانعكاس يمكن الحصول على جميع معلومات الدالة، ويمكن أيضًا استدعاء الدالة بالانعكاس.

المعلومات

الحصول على جميع معلومات الدالة من خلال نوع الانعكاس:

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

func main() {
   rType := reflect.TypeOf(Max)
   // طباعة اسم الدالة، الدوال الحرفية ليس لها اسم
   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

الاستدعاء

استدعاء الدالة من خلال قيمة الانعكاس:

go
func (v Value) Call(in []Value) []Value
go
func main() {
   // الحصول على قيمة انعكاس الدالة
   rType := reflect.ValueOf(Max)
   // تمرير مصفوفة المعاملات
   rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
   for _, value := range rResValue {
      fmt.Println(value.Interface())
   }
}

الإخراج:

50

الهيكل

لنفترض وجود الهيكل التالي:

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
  // هل هو حقل متداخل
  Anonymous bool
}

هناك طريقتان للوصول لحقول الهيكل: الأولى من خلال الفهرس، والثانية من خلال الاسم.

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

مثال الوصول من خلال الفهرس:

go
func main() {
  rType := reflect.TypeOf(new(Person)).Elem()
  // طباعة عدد حقول الهيكل
  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()
   // طباعة عدد حقول الهيكل
   fmt.Println(rType.NumField())
   if field, ok := rType.FieldByName("money"); ok {
      fmt.Println(field.Name, field.Type, field.IsExported())
   }
}

الإخراج:

4
money int false

تعديل الحقول

إذا أردنا تعديل قيم حقول الهيكل، يجب تمرير مؤشر هيكل. فيما يلي مثال لتعديل حقل:

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")
  }
  // طباعة الهيكل
  fmt.Println(rValue.Interface())
}

الإخراج:

{jack 0  0}

بالنسبة لتعديل الحقول الخاصة للهيكل، نحتاج لبعض العمليات الإضافية، كالتالي:

go
func main() {
  // تمرير المؤشر
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // الحصول على حقل خاص
  money := rValue.FieldByName("money")
  // تعديل قيمة الحقل
  if (money != reflect.Value{}) {
    // إنشاء قيمة انعكاس مؤشر للحقل غير المُصدَّر في الهيكل
    p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
    // الحصول على العنصر الذي يشير إليه هذا المؤشر، أي الحقل المراد تعديله
    field := p.Elem()
    // تعديل القيمة
    field.SetInt(164)
  }
  // طباعة الهيكل
  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
  // الدالة المقابلة للدالة، المعامل الأول هو المستقبل
  Func  Value
  // الفهرس
  Index int
}

مثال الوصول لمعلومات الدالة:

go
func main() {
  // الحصول على نوع انعكاس الهيكل
  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() {
  // الحصول على نوع انعكاس الهيكل
  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، أي نوع المستقبل:

1
0 Talk func(main.Person, string) string true
معاملات الدالة
main.Person
string
قيم إرجاع الدالة
string

استدعاء الدوال

استدعاء الدوال مشابه لاستدعاء الدوال العادية، ولا يحتاج لتمرير المستقبل يدويًا، مثال:

go
func main() {
   // الحصول على نوع انعكاس الهيكل
   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!

الإنشاء

من خلال الانعكاس يمكن إنشاء قيم جديدة، وحزمة reflect توفر أيضًا دوال مختلفة أكثر راحة لبعض الأنواع الخاصة.

الأنواع الأساسية

go
// تعيد قيمة انعكاس مؤشر لقيمة الانعكاس
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!

الهيكل

إنشاء الهيكل يستخدم أيضًا دالة 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() {
   // إنشاء قيمة انعكاس للهيكل
   rType := reflect.TypeOf(new(Person)).Elem()
   person := reflect.New(rType).Elem()
   fmt.Println(person.Interface())
}

الإخراج:

{ 0  0}

الشريحة

إنشاء شريحة بالانعكاس:

go
func MakeSlice(typ Type, len, cap int) Value
go
func main() {
   // إنشاء قيمة انعكاس للشريحة
   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)

إنشاء خريطة بالانعكاس:

go
func MakeMapWithSize(typ Type, n int) Value
go
func main() {
   // إنشاء قيمة انعكاس للخريطة
   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]

القناة

إنشاء قناة بالانعكاس:

go
func MakeChan(typ Type, buffer int) Value
go
func main() {
   // إنشاء قيمة انعكاس للقناة
   makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
   fmt.Println(makeChan.Interface())
}

الدالة

إنشاء دالة بالانعكاس:

go
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
go
func main() {
    // تمرير نوع الغلاف وجسم الدالة
  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 هي دالة توفرها حزمة الانعكاس لتحديد ما إذا كان متغيران متساويين تمامًا، توقيعها كالتالي:

go
func DeepEqual(x, y any) bool

هذه الدالة تعالج كل نوع أساسي، وفيما يلي بعض طرق الحكم للأنواع:

  • المصفوفات: كل عنصر في المصفوفة متساوٍ تمامًا
  • الشرائح: عندما يكون كلاهما nil، يُحكم بالتساوي التام، أو عندما يكون كلاهما غير فارغ، العناصر في نطاق الطول متساوية تمامًا
  • الهياكل: جميع الحقول متساوية تمامًا
  • الخرائط: عندما يكون كلاهما nil، يُحكم بالتساوي التام، عندما يكون كلاهما غير nil، كل قيمة يرسمها كل مفتاح متساوية تمامًا
  • المؤشرات: تشير لنفس العنصر أو العناصر التي تشير إليها متساوية تمامًا
  • الواجهات: عندما يكون النوع المحدد للواجهة متساويًا تمامًا
  • الدوال: فقط عندما يكون كلاهما nil يكونان متساويين تمامًا، وإلا فليسا متساويين

فيما يلي بعض الأمثلة:

الشرائح

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

الإخراج:

true

الهياكل

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 تم تحريره بواسطة www.golangdev.cn