A detailed explanation of the reflective usage of Go language study notes

  • 2020-06-03 06:56:13
  • OfStack

An example of Go study notes is given in this paper. To share for your reference, specific as follows:

1. Type (Type)

Reflection (reflect) compensates for the dynamic behavior of static languages by allowing us to discover object type information and memory structure at runtime. At the same time, reflection is also an important means of implementing metaprogramming.

Like C data structure 1, the Go object header does not have a type pointer, and by itself is not informed of any type information at run time. All the information needed for reflection operations is derived from interface variables. Interface variables store their own types as well as the type data of the actual object.

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

These two reflection entry functions convert any incoming objects to the interface type.
When dealing with types, you need to differentiate Type and Kind . The former represents real types (static types) and the latter its infrastructure (underlying types) category, base types.

type X int
func main() {
    var a X = 100
    t := reflect.TypeOf(a)
    fmt.Println(t)
    fmt.Println(t.Name(), t.Kind())
}

Output:


X int

Therefore in the type judgment, must choose the correct way

type X int
type Y int
func main() {
    var a, b X = 100, 200
    var c Y = 300
    ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)
    fmt.Println(ta == tb, ta == tc)
    fmt.Println(ta.Kind() == tc.Kind())
}

In addition to getting the type from the actual object, you can also directly construct some basic composite types.
func main() {
    a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
    m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
    fmt.Println(a, m)
}

Output:


[10]uint8   map[string]int

An incoming object should distinguish between a base type and a pointer type because they do not belong to the same type.

func main() {
    x := 100
    tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
    fmt.Println(tx, tp, tx == tp)
    fmt.Println(tx.Kind(), tp.Kind())
    fmt.Println(tx == tp.Elem())
}

Output:


int *int false
int ptr
true

The method Elem() returns the base type of a pointer, array, slice, dictionary (value), or channel.

func main() {
    fmt.Println(reflect.TypeOf(map[string]int{}).Elem())
    fmt.Println(reflect.TypeOf([]int32{}).Elem())
}

Output:


int
int32

Only after getting the base type of a pointer to a structure can its fields be traversed.


type user struct {
    name string
    age int
}
type manager struct {
    user
    title string
}
func main() {
    var m manager
    t := reflect.TypeOf(&m)
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Println(f.Name, f.Type, f.Offset)
        if f.Anonymous { // Output anonymous field structure
            for x := 0; x < f.Type.NumField(); x++ {
                af := f.Type.Field(x)
                fmt.Println(" ", af.Name, af.Type)
            }
        }
    }
}

Output:


user main.user 0
 name string
 age int
title string 24

For anonymous fields, you can access them directly by multiple indexes (in the order defined).

type user struct {
    name string
    age  int
}
type manager struct {
    user
    title string
}
func main() {
    var m manager
    t := reflect.TypeOf(m)
    name, _ := t.FieldByName("name") // Look up by name
    fmt.Println(name.Name, name.Type)
    age := t.FieldByIndex([]int{0, 1}) // Look up by multiple indexes
    fmt.Println(age.Name, age.Type)
}

Output:


name string
age int

FieldByName() does not support multilevel names, and if there is a shadowing with the same name, it must be obtained twice through an anonymous field.

Similarly, when outputting a method set, 1 sample distinguishes between base and pointer types.

type A int
type B struct {
    A
}
func (A) av() {}
func (*A) ap() {}
func (B) bv() {}
func (*B) bp() {}
func main() {
    var b B
    t := reflect.TypeOf(&b)
    s := []reflect.Type{t, t.Elem()}
    for _, t2 := range s {
        fmt.Println(t2, ":")
        for i := 0; i < t2.NumMethod(); i++ {
            fmt.Println(" ", t2.Method(i))
        }
    }
}

Output:


*main.B :
  {ap main func(*main.B) <func(*main.B) Value> 0}
  {av main func(*main.B) <func(*main.B) Value> 1}
  {bp main func(*main.B) <func(*main.B) Value> 2}
  {bv main func(*main.B) <func(*main.B) Value> 3}   
main.B :
  {av main func(*main.B) <func(*main.B) Value> 0} 
  {bv main func(*main.B) <func(*main.B) Value> 1}

One difference, and one thought, is that reflection can detect non-exported structure members of the current package or outsourcing.

import (
    "net/http"
    "reflect"
    "fmt"
)
func main()  {
    var s http.Server
    t := reflect.TypeOf(s)
    for i := 0; i < t.NumField(); i++ {
        fmt.Println(t.Field(i).Name)
    }
}

Output:


Addr
Handler
ReadTimeout
WriteTimeout
TLSConfig
MaxHeaderBytes
TLSNextProto
ConnState
ErrorLog
disableKeepAlives
nextProtoOnce
nextProtoErr

Both the current package and outsourcing are "outsourced" to reflect.
struct tag can be extracted by reflection and decomposed automatically. It is often used for ORM mapping, or data format validation.


type user struct {
    name string `field:"name" type:"varchar(50)"`
    age  int `field:"age" type:"int"`
}
func main() {
    var u user
    t := reflect.TypeOf(u)
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Printf("%s: %s %s\n", f.Name, f.Tag.Get("field"), f.Tag.Get("type"))
    }
}

Output:


name: name varchar(50)
age: age int

Auxiliary judgment methods Implements(), ConvertibleTo, and AssignableTo() are required for dynamic invocation and assignment at runtime.

type X int
func (X) String() string {
    return ""
}
func main()  {
    var a X
    t := reflect.TypeOf(a)
    // Implements You can't use the type as a parameter directly, which makes this usage awkward
    st := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
    fmt.Println(t.Implements(st))
    it := reflect.TypeOf(0)
    fmt.Println(t.ConvertibleTo(it))
    fmt.Println(t.AssignableTo(st), t.AssignableTo(it))
}

Output:


true
true
true false

2. The value (Value)

Unlike Type, Value focuses on reading and writing object instance data.
As mentioned in the previous section, interface variables copy objects and are unaddressable's, so you must use Pointers to modify the target object.

func main()  {
    a := 100
    va, vp := reflect.ValueOf(a), reflect.ValueOf(&a).Elem()
    fmt.Println(va.CanAddr(), va.CanSet())
    fmt.Println(vp.CanAddr(), vp.CanSet())
}

Output:


[10]uint8   map[string]int

0

Even if the pointer is passed in, the 1 sample needs to pass through Elem() Get the target object. Because the pointer stored by the interface itself cannot be addressed and set up.
Note that non-exported fields cannot be set directly, either in the current package or outsourced.

type User struct {
    Name string
    code int
}
func main() {
    p := new(User)
    v := reflect.ValueOf(p).Elem()
    name := v.FieldByName("Name")
    code := v.FieldByName("code")
    fmt.Printf("name: canaddr = %v, canset = %v\n", name.CanAddr(), name.CanSet())
    fmt.Printf("code: canaddr = %v, canset = %v\n", code.CanAddr(), code.CanSet())
    if name.CanSet() {
        name.SetString("Tom")
    }
    if code.CanAddr() {
        *(*int)(unsafe.Pointer(code.UnsafeAddr())) = 100
    }
    fmt.Printf("%+v\n", *p)
}

Output:


[10]uint8   map[string]int

1

Method types such as ES123en.Pointer and ES125en.Int, which convert data stored in ES127en.data to a pointer, the target must be a pointer type. UnsafeAddr returns any CanAddr Value.data address (equivalent to & The fetch operation), such as Value after Elem(), and the field member address.

In the case of a pointer type field in a structure, Pointer returns the address that the field holds, and UnsafeAddr returns the address of the field itself (structure object address + offset).
Type recommendation and conversion can be done using the Interface method.

func main() {
    type user struct {
        Name string
        Age  int
    }
    u := user{
        "q.yuhen",
        60,
    }
    v := reflect.ValueOf(&u)
    if !v.CanInterface() {
        println("CanInterface: fail.")
        return
    }
    p, ok := v.Interface().(*user)
    if !ok {
        println("Interface: fail.")
        return
    }
    p.Age++
    fmt.Printf("%+v\n", u)
}

Output:


[10]uint8   map[string]int

2

You can also directly use Value.Int, Bool and other methods for type conversion, but pani will be triggered when failure occurs, and ES153en-ES154en is not supported.

Example of setting a compound type object:

func main()  {
    c := make(chan int, 4)
    v := reflect.ValueOf(c)
    if v.TrySend(reflect.ValueOf(100)) {
        fmt.Println(v.TryRecv())
    }
}

Output:


100 true

Interfaces have two nil states, which is always a potential problem. The solution is to use IsNil() to determine if the value is nil.

func main()  {
    var a interface{} = nil
    var b interface{} = (*int)(nil)
    fmt.Println(a == nil)
    fmt.Println(b == nil, reflect.ValueOf(b).IsNil())
}

Output:


[10]uint8   map[string]int

4

iface. data can also be converted directly to determine whether iface. data is zero.

func main()  {
    var b interface{} = (*int)(nil)
    iface := (*[2]uintptr)(unsafe.Pointer(&b))
    fmt.Println(iface, iface[1] == 0)
}

Output:


[10]uint8   map[string]int

5

Unfortunately, some of the methods in Value do not implement ok-ES183en or return error, so you have to decide for yourself whether the return is Zero Value.

func main()  {
    v := reflect.ValueOf(struct {name string}{})
    println(v.FieldByName("name").IsValid())
    println(v.FieldByName("xxx").IsValid())
}

Output:


[10]uint8   map[string]int

6

Method 3.

Invoking methods dynamically is not much of a hassle. Simply press the In list to prepare the required parameters.

type X int
type Y int
func main() {
    var a, b X = 100, 200
    var c Y = 300
    ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)
    fmt.Println(ta == tb, ta == tc)
    fmt.Println(ta.Kind() == tc.Kind())
}
8

Output:


[10]uint8   map[string]int

7

For variable parameters, CallSlice() is more convenient 1.

type X int
type Y int
func main() {
    var a, b X = 100, 200
    var c Y = 300
    ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)
    fmt.Println(ta == tb, ta == tc)
    fmt.Println(ta.Kind() == tc.Kind())
}
9

Output:


[x = 100]
[x = 100]

You cannot call non-exported methods or even get a valid address.

4. Build

The reflection library provides built-in functions make() and new() Of these, the most interesting is MakeFunc() . It can be used to implement generic templates that can be adapted to different data types.

func main() {
    a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
    m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
    fmt.Println(a, m)
}
0

Output:


[10]uint8   map[string]int

9

If the language supports generics, you don't have to do that

I hope that this article is helpful for Go language programming.


Related articles: