Skip to content

Riflessione

La riflessione è un meccanismo che consente di ispezionare la struttura del linguaggio stesso a runtime. Può affrontare alcuni problemi in modo molto flessibile, ma allo stesso tempo presenta anche evidenti svantaggi, come problemi di prestazioni, ecc. In Go, la riflessione è strettamente correlata a interface{}. In gran parte, ovunque appaia interface{}, ci sarà riflessione. Le API di riflessione in Go sono fornite dal pacchetto standard reflect.

Interfacce

Prima di iniziare, diamo una semplice occhiata a due interfacce nel pacchetto runtime. In Go, le interfacce sono essenzialmente strutture. Go divide le interfacce in due grandi categorie a runtime: interfacce senza insieme di metodi e interfacce con insieme di metodi. Per le interfacce con insieme di metodi, a runtime sono rappresentate dalla seguente struttura iface:

go
type iface struct {
   tab  *itab // contiene tipo di dati, tipo di interfaccia, insieme di metodi, ecc.
   data unsafe.Pointer // puntatore al valore
}

Per le interfacce senza insieme di metodi, a runtime sono rappresentate dalla struttura eface, come segue:

go
type eface struct {
   _type *_type // tipo
   data  unsafe.Pointer // puntatore al valore
}

Queste due strutture hanno tipi di struttura corrispondenti nel pacchetto reflect. iface corrisponde a nonEmptyInterface:

go
type nonEmptyInterface struct {
  itab *struct {
    ityp *rtype // tipo di interfaccia statica
    typ  *rtype // tipo concreto dinamico
    hash uint32 // hash del tipo
    _    [4]byte
    fun  [100000]unsafe.Pointer // insieme di metodi
  }
  word unsafe.Pointer // puntatore al valore
}

E eface corrisponde a emptyInterface:

go
type emptyInterface struct {
   typ  *rtype // tipo concreto dinamico
   word unsafe.Pointer // valore che punta al puntatore
}

Per questi due tipi, l'ufficiale ha dato definizioni molto chiare:

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

Come menzionato sopra, il tipo concreto dinamico, il termine originale è dynamic concrete type. Innanzitutto, Go è un linguaggio di tipi statici al 100%. La staticità si riflette nel fatto che il tipo di interfaccia astratto manifestato esternamente è invariabile, mentre dinamico indica che il tipo concreto memorizzato sottostante dell'interfaccia può cambiare. Fin qui, per il semplice principio delle interfacce, è sufficiente conoscere quanto sopra per soddisfare l'apprendimento successivo della riflessione.

Ponte

Nel pacchetto reflect, ci sono rispettivamente il tipo di interfaccia reflect.Type per rappresentare i tipi in Go e il tipo di struttura reflect.Value per rappresentare i valori in Go:

go
type Type interface {
    ...

    Name() string

  PkgPath() string

  Size() uintptr

  String() string

  Kind() Kind

    ...
}

type Value struct {

   typ *rtype

   ptr unsafe.Pointer

   flag

}

Il codice sopra omette molti dettagli. Per ora, basta conoscere l'esistenza di questi due tipi. Tutte le operazioni di riflessione in Go si basano su questi due tipi. Il pacchetto reflect fornisce due funzioni per convertire i tipi in Go nei due tipi sopra menzionati per eseguire operazioni di riflessione: reflect.TypeOf:

go
func TypeOf(i any) Type

E reflect.ValueOf:

go
func ValueOf(i any) Value

Come si può vedere, i tipi di parametro di entrambe le funzioni sono any, ovvero l'alias di interface{}. Se si desidera eseguire operazioni di riflessione, è necessario prima convertire il tipo in interface{}. Questo è il motivo per cui è stato menzionato in precedenza che la riflessione non può fare a meno dell'interfaccia vuota. Non rigorosamente parlando, l'interfaccia vuota è il ponte che collega il sistema di tipi di Go e la riflessione. La figura seguente descrive vividamente il processo:

TIP

Nel testo seguente, per comodità, useremo uniformemente l'alias any per sostituire interface{}

Nucleo

In Go ci sono tre leggi classiche della riflessione. Combinando con quanto sopra, sono molto facili da capire:

  1. La riflessione può convertire una variabile di tipo interface{} in un oggetto di riflessione
  2. La riflessione può ripristinare un oggetto di riflessione in una variabile di tipo interface{}
  3. Per modificare un oggetto di riflessione, il suo valore deve essere impostabile

Queste tre leggi sono il nucleo della riflessione in Go. Quando è necessario accedere alle informazioni relative al tipo, è necessario utilizzare reflect.TypeOf. Quando è necessario modificare il valore di riflessione, è necessario utilizzare reflect.ValueOf.

Tipo

reflect.Type rappresenta un tipo in Go. Utilizza la funzione reflect.TypeOf() per convertire una variabile in reflect.Type. Esempio di codice:

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

Risultato output:

string

Kind

Per Type, Go utilizza internamente reflect.Kind per rappresentare i tipi di base in Go. Essenzialmente è un intero senza segno uint:

go
type Kind uint

Il pacchetto reflect utilizza Kind per enumerare tutti i tipi di base in Go, come mostrato di seguito:

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
)

Il tipo Kind implementa solo il metodo String() dell'interfaccia Stringer. Questo tipo ha solo questo metodo. Il valore di ritorno del metodo String() proviene da una slice interna, come mostrato di seguito. Questa scrittura a prima vista sembra molto simile a una map, ma in realtà è una scrittura speciale in Go: sintassi di inizializzazione dell'indice (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
}

Attraverso Kind, è possibile sapere quale tipo di base è effettivamente memorizzato nella variabile any. Ad esempio:

go
func main() {
    // Dichiara una variabile di tipo any
  var eface any
    // Assegna un valore
  eface = 100
    // Ottieni il suo tipo tramite il metodo Kind
  fmt.Println(reflect.TypeOf(eface).Kind())
}

Risultato output:

int

Elem

go
type Type interface{
    Elem() Type
}

Utilizzando il metodo Type.Elem(), è possibile determinare il tipo di elemento memorizzato nella struttura dati di tipo any. I tipi di parametro sottostanti accettati devono essere uno tra puntatore, slice, array, canale, mappa. Altrimenti si verificherà panic. Di seguito è riportato un esempio di codice:

go
func main() {
  var eface any
  eface = map[string]int{}
  rType := reflect.TypeOf(eface)
    // key() restituisce il tipo di riflessione della chiave della map
  fmt.Println(rType.Key().Kind())
  fmt.Println(rType.Elem().Kind())
}

Output:

string
int

Anche un puntatore può essere inteso come un contenitore. Per un puntatore, l'uso di Elem() otterrà il tipo di riflessione dell'elemento puntato. Esempio di codice:

go
func main() {
  var eface any
    // Assegna un puntatore
  eface = new(strings.Builder)
  rType := reflect.TypeOf(eface)
    // Ottieni il tipo di riflessione dell'elemento puntato dal puntatore
  vType := rType.Elem()
    // Output del percorso del pacchetto
  fmt.Println(vType.PkgPath())
    // Output del nome
  fmt.Println(vType.Name())
}
strings
Builder

Per array, slice e canali, l'uso è simile.

Size

go
type Type interface{
    Size() uintptr
}

Attraverso il metodo Size, è possibile ottenere la dimensione in byte del tipo corrispondente. Esempio:

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())
}

Risultato output:

8
16
16
8
24

TIP

L'uso di unsafe.Sizeof() può ottenere lo stesso effetto.

Comparable

go
type Type interface{
    Comparable() bool
}

Attraverso il metodo Comparable, è possibile determinare se un tipo può essere confrontato. Esempio:

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())
}

Output:

true
true
false
true

Implements

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

Attraverso il metodo Implements, è possibile determinare se un tipo implementa una determinata interfaccia:

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))
}

Risultato output:

true
false

ConvertibleTo

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

Utilizzando il metodo ConvertibleTo, è possibile determinare se un tipo può essere convertito in un altro tipo specificato:

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))
}

Output:

true
false

Valore

reflect.Value rappresenta il valore dell'interfaccia di riflessione. Utilizza la funzione reflect.ValueOf() per convertire una variabile in reflect.Value. Esempio di codice:

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

Risultato output:

hello world!

Type

go
func (v Value) Type() Type

Il metodo Type può ottenere il tipo di un valore di riflessione:

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

Output:

int

Elem

go
func (v Value) Elem() Value

Ottieni il valore di riflessione dell'elemento di un valore di riflessione:

go
func main() {
   num := new(int)
   *num = 114514
   // Prendiamo un puntatore come esempio
   rValue := reflect.ValueOf(num).Elem()
   fmt.Println(rValue.Interface())
}

Output:

114514

Puntatore

Ci sono due modi per ottenere il puntatore di un valore di riflessione:

go
// Restituisce un valore di riflessione puntatore che rappresenta l'indirizzo di v
func (v Value) Addr() Value

// Restituisce un uintptr che punta al valore originale di v, equivalente a uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr

// Restituisce un uintptr che punta al valore originale di v
// Solo quando il Kind di v è Chan, Func, Map, Pointer, Slice, UnsafePointer, altrimenti panic
func (v Value) Pointer() uintptr

// Restituisce un unsafe.Pointer che punta al valore originale di v
// Solo quando il Kind di v è Chan, Func, Map, Pointer, Slice, UnsafePointer, altrimenti panic
func (v Value) UnsafePointer() unsafe.Pointer

Esempio:

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())
}

Output:

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

TIP

fmt.Println otterrà il tipo del parametro tramite riflessione. Se è di tipo reflect.Value, chiamerà automaticamente Value.Interface() per ottenere il suo valore originale.

Proviamo con una 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())
}

Output:

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

Impostare Valore

go
func (v Value) Set(x Value)

Se si desidera modificare il valore di riflessione tramite riflessione, il suo valore deve essere indirizzabile. In questo caso, si dovrebbe modificare il valore dell'elemento tramite un puntatore, invece di tentare direttamente di modificare il valore dell'elemento.

go
func main() {
   // *int
   num := new(int)
   *num = 114514
   rValue := reflect.ValueOf(num)
    // Ottieni l'elemento puntato dal puntatore
   ele := rValue.Elem()
   fmt.Println(ele.Interface())
   ele.SetInt(11)
   fmt.Println(ele.Interface())
}

Output:

114514
11

Ottenere Valore

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

Attraverso il metodo Interface(), è possibile ottenere il valore originale del valore di riflessione:

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

Output:

hello

Funzione

Attraverso la riflessione è possibile ottenere tutte le informazioni su una funzione e anche chiamare la funzione tramite riflessione.

Informazioni

Ottieni tutte le informazioni sulla funzione tramite il tipo di riflessione:

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

func main() {
   rType := reflect.TypeOf(Max)
   // Output del nome della funzione. Le funzioni letterali non hanno nome
   fmt.Println(rType.Name())
   // Output del numero di parametri e valori di ritorno
   fmt.Println(rType.NumIn(), rType.NumOut())
   rParamType := rType.In(0)
   // Output del tipo del primo parametro
   fmt.Println(rParamType.Kind())
   rResType := rType.Out(0)
   // Output del tipo del primo valore di ritorno
   fmt.Println(rResType.Kind())
}

Output:


2 1
int
int

Chiamata

Chiama la funzione tramite il valore di riflessione:

go
func (v Value) Call(in []Value) []Value
go
func main() {
   // Ottieni il valore di riflessione della funzione
   rType := reflect.ValueOf(Max)
   // Passa l'array di parametri
   rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
   for _, value := range rResValue {
      fmt.Println(value.Interface())
   }
}

Output:

50

Struttura

Supponiamo di avere la seguente struttura:

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
}

Accedere ai Campi

La struttura reflect.StructField è la seguente:

go
type StructField struct {
  // Nome del campo
  Name string
  // Nome del pacchetto
  PkgPath string
  // Nome del tipo
  Type      Type
  // Tag
  Tag       StructTag
  // Offset in byte del campo
  Offset    uintptr
  // Indice
  Index     []int
  // Se è un campo annidato
  Anonymous bool
}

Ci sono due modi per accedere ai campi di una struttura: uno è tramite indice, l'altro è tramite nome.

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

Esempio di accesso tramite indice:

go
func main() {
  rType := reflect.TypeOf(new(Person)).Elem()
  // Output del numero di campi della struttura
  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())
  }
}

Output:

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)
}

Esempio di accesso tramite nome:

go
func main() {
   rType := reflect.TypeOf(new(Person)).Elem()
   // Output del numero di campi della struttura
   fmt.Println(rType.NumField())
   if field, ok := rType.FieldByName("money"); ok {
      fmt.Println(field.Name, field.Type, field.IsExported())
   }
}

Output:

4
money int false

Modificare Campi

Se si desidera modificare il valore di un campo di struttura, è necessario passare un puntatore alla struttura. Di seguito è riportato un esempio di modifica di un campo:

go
func main() {
  // Passa un puntatore
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Ottieni il campo
  name := rValue.FieldByName("Name")
  // Modifica il valore del campo
  if (name != reflect.Value{}) { // Se restituisce reflect.Value{}, significa che il campo non esiste
    name.SetString("jack")
  }
  // Output della struttura
  fmt.Println(rValue.Interface())
}

Output:

{jack 0  0}

Per modificare i campi privati di una struttura, sono necessarie alcune operazioni extra, come segue:

go
func main() {
  // Passa un puntatore
  rValue := reflect.ValueOf(&Person{
    Name:    "",
    Age:     0,
    Address: "",
    money:   0,
  }).Elem()

  // Ottieni un campo privato
  money := rValue.FieldByName("money")
  // Modifica il valore del campo
  if (money != reflect.Value{}) {
    // Costruisci un valore di riflessione puntatore che punta al campo non esportato della struttura
    p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
    // Ottieni l'elemento puntato dal puntatore, ovvero il campo da modificare
    field := p.Elem()
    // Modifica il valore
    field.SetInt(164)
  }
  // Output della struttura
  fmt.Printf("%+v\n", rValue.Interface())
}

Accedere ai Tag

Dopo aver ottenuto StructField, è possibile accedere direttamente al suo Tag:

go
// Se non esiste, ok è false
func (tag StructTag) Lookup(key string) (value string, ok bool)

// Se non esiste, restituisce una stringa vuota
func (tag StructTag) Get(key string) string

Esempio:

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"))
   }
}

Output:

name true
name

Accedere ai Metodi

L'accesso ai metodi è simile al processo di accesso ai campi, ma le firme delle funzioni sono leggermente diverse. La struttura reflect.Method è la seguente:

go
type Method struct {
  // Nome del metodo
  Name string
  // Nome del pacchetto
  PkgPath string
  // Tipo del metodo
  Type  Type
  // Funzione corrispondente al metodo, il primo parametro è il receiver
  Func  Value
  // Indice
  Index int
}

Esempio di accesso alle informazioni sul metodo:

go
func main() {
  // Ottieni il tipo di riflessione della struttura
  rType := reflect.TypeOf(new(Person)).Elem()
  // Output del numero di metodi
  fmt.Println(rType.NumMethod())
  // Itera e output delle informazioni sul metodo
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
  }
}

Output:

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

Se si desidera ottenere i dettagli dei parametri e dei valori di ritorno del metodo, è possibile ottenerli tramite Method.Func. Il processo è simile all'accesso alle informazioni sulla funzione. Modificando leggermente il codice sopra:

go
func main() {
  // Ottieni il tipo di riflessione della struttura
  rType := reflect.TypeOf(new(Person)).Elem()
  // Output del numero di metodi
  fmt.Println(rType.NumMethod())
  // Itera e output delle informazioni sul metodo
  for i := 0; i < rType.NumMethod(); i++ {
    method := rType.Method(i)
    fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
    fmt.Println("Parametri del metodo")
    for i := 0; i < method.Func.Type().NumIn(); i++ {
      fmt.Println(method.Func.Type().In(i).String())
    }
    fmt.Println("Valori di ritorno del metodo")
    for i := 0; i < method.Func.Type().NumOut(); i++ {
      fmt.Println(method.Func.Type().Out(i).String())
    }
  }
}

Come si può vedere, il primo parametro è main.Person, ovvero il tipo di receiver:

1
0 Talk func(main.Person, string) string true
Parametri del metodo
main.Person
string
Valori di ritorno del metodo
string

Chiamare Metodi

Chiamare un metodo è simile al processo di chiamata di una funzione, e non è necessario passare manualmente il receiver. Esempio:

go
func main() {
   // Ottieni il tipo di riflessione della struttura
   rValue := reflect.ValueOf(new(Person)).Elem()
   // Output del numero di metodi
   fmt.Println(rValue.NumMethod())
   // Itera e output delle informazioni sul metodo
   talk := rValue.MethodByName("Talk")
   if (talk != reflect.Value{}) {
      // Chiama il metodo e ottieni il valore di ritorno
      res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
      // Itera e output dei valori di ritorno
      for _, re := range res {
         fmt.Println(re.Interface())
      }
   }
}

Output:

1
hello,reflect!

Creazione

Attraverso la riflessione è possibile costruire nuovi valori. Il pacchetto reflect fornisce contemporaneamente funzioni più convenienti per alcuni tipi speciali.

Tipi di Base

go
// Restituisce un valore di riflessione puntatore che punta al valore di riflessione
func New(typ Type) Value

Prendiamo string come esempio:

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

Struttura

Anche la creazione di una struttura utilizza la funzione 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() {
   // Crea un valore di riflessione della struttura
   rType := reflect.TypeOf(new(Person)).Elem()
   person := reflect.New(rType).Elem()
   fmt.Println(person.Interface())
}

Output:

{ 0  0}

Slice

Creazione di slice tramite riflessione:

go
func MakeSlice(typ Type, len, cap int) Value
go
func main() {
   // Crea un valore di riflessione della slice
   rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
   // Itera e assegna valori
   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

Creazione di Map tramite riflessione:

go
func MakeMapWithSize(typ Type, n int) Value
go
func main() {
   // Costruisci un valore di riflessione della map
   rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
   // Imposta il valore
   rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
   fmt.Println(rValue.Interface())
}
map[a:1]

Canale

Creazione di canali tramite riflessione:

go
func MakeChan(typ Type, buffer int) Value
go
func main() {
   // Crea un valore di riflessione del canale
   makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
   fmt.Println(makeChan.Interface())
}

Funzione

Creazione di funzioni tramite riflessione:

go
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
go
func main() {
    // Passa il tipo di wrapper e il corpo della funzione
  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)})
}

Output:

func(int)
1024

Completamente Uguale

reflect.DeepEqual è una funzione fornita dal pacchetto di riflessione per determinare se due variabili sono completamente uguali. La firma è la seguente:

go
func DeepEqual(x, y any) bool

Questa funzione gestisce ogni tipo di base separatamente. Di seguito sono riportati alcuni modi di giudizio per tipo:

  • Array: ogni elemento nell'array è completamente uguale
  • Slice: quando entrambi sono nil, sono giudicati completamente uguali, oppure quando entrambi non sono vuoti, gli elementi nell'intervallo di lunghezza sono completamente uguali
  • Struttura: tutti i campi sono completamente uguali
  • Mappa: quando entrambi sono nil, sono completamente uguali. Quando entrambi non sono nil, ogni valore mappato da ogni chiave è completamente uguale
  • Puntatore: puntano allo stesso elemento o gli elementi puntati sono completamente uguali
  • Interfaccia: quando il tipo concreto dell'interfaccia è completamente uguale
  • Funzione: solo quando entrambi sono nil sono completamente uguali, altrimenti non sono completamente uguali

Di seguito sono riportati alcuni esempi:

Slice

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

Output:

true

Struttura

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))
}

Output:

false
false
true

Golang by www.golangdev.cn edit