Reflection of Go language foundation

Keywords: Go

Reflection of 09Go language foundation

Internal mechanism of variables

Variables in Go language are divided into two parts:

  • Type information: predefined meta information.
  • Value information: it can change dynamically during program operation.

Reflection introduction

Reflection refers to the ability to access and modify the program itself during the running period of the program. When the program is compiled, the variable is converted to the memory address, and the variable name will not be written to the executable part by the compiler. While running the program, the program cannot get its own information.

Languages that support reflection can integrate the reflection information of variables, such as field name, type information and structure information, into the executable file during program compilation, and provide the program with an interface to access the reflection information, so that the reflection information of types can be obtained during program run-time and can be modified.

The Go program uses the reflect package to access the reflection information of the program at run time.

An empty interface can store variables of any type. How do we know what data this empty interface holds? Reflection is to dynamically obtain the type information and value information of a variable at runtime.

reflect package

In the reflection mechanism of Go language, any interface Value is composed of a specific Type and a specific Type Value (we introduced related concepts in the previous interface blog). In the Go language, the functions related to reflection are provided by the built-in reflect package. Any interface Value can be understood as composed of reflect.Type and reflect.Value in reflection, and the reflect package provides two functions: reflect.TypeOf and reflect.ValueOf to obtain the Value and Type of any object.

TypeOf

In Go language, use the reflect.TypeOf() function to obtain the type object (reflect.Type) of any value, and the program can access the type information of any value through the type object.

package main

import (
	"fmt"
	"reflect"
)

func reflectType(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Printf("type:%v\n", v)
}
func main() {
	var a float32 = 3.14
	reflectType(a) // type:float32
	var b int64 = 100
	reflectType(b) // type:int64
} 

type name and type kind

In reflection, there are also two types of types: type and Kind. In Go language, we can use the type keyword to construct many user-defined types, and Kind refers to the underlying type. However, in reflection, when it is necessary to distinguish between large types such as pointers and structures, Kind will be used. For example, we define two pointer types and two struct types, and view their types and types through reflection.

package main

import (
	"fmt"
	"reflect"
)

type myInt int64

func reflectType(x interface{}) {
	t := reflect.TypeOf(x)
	fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}

func main() {
	var a *float32 // Pointer
	var b myInt    // Custom type
	var c rune     // Type alias
	reflectType(a) // type: kind:ptr
	reflectType(b) // type:myInt kind:int64
	reflectType(c) // type:int32 kind:int32

	type person struct {
		name string
		age  int
	}
	type book struct{ title string }
	var d = person{
		name: "Shahe little prince",
		age:  18,
	}
	var e = book{title: "<Learn from the little prince Go Language"}
	reflectType(d) // type:person kind:struct
	reflectType(e) // type:book kind:struct
} 

In the reflection of Go language, for variables of types such as array, slice, Map and pointer, their. Name() returns null.

The Kind types defined in the reflect package are as follows:

type Kind uint
const (
    Invalid Kind = iota  // Illegal type
    Bool                 // Boolean
    Int                  // signed int 
    Int8                 // Signed 8-bit integer
    Int16                // Signed 16 bit integer
    Int32                // Signed 32-bit integer
    Int64                // Signed 64 bit integer
    Uint                 // unsigned int 
    Uint8                // Unsigned 8-bit integer
    Uint16               // Unsigned 16 bit integer
    Uint32               // Unsigned 32-bit integer
    Uint64               // Unsigned 64 bit integer
    Uintptr              // Pointer
    Float32              // Single-precision floating-point 
    Float64              // Double precision floating point number
    Complex64            // 64 bit complex type
    Complex128           // 128 bit complex type
    Array                // array
    Chan                 // passageway
    Func                 // function
    Interface            // Interface
    Map                  // mapping
    Ptr                  // Pointer
    Slice                // section
    String               // character string
    Struct               // structural morphology
    UnsafePointer        // Bottom pointer
) 

ValueOf

reflect.ValueOf() returns the type reflect. Value, which contains the value information of the original value. The reflect.Value and the original value can be converted to each other.

The method to obtain the original value provided by the reflect.Value type is as follows:

method explain
Interface() interface {} Returns the value as interface {} type, which can be converted to the specified type through type assertion
Int() int64 Returns a value as an int, which can be returned by all signed integers
Uint() uint64 Returns the value as uint, which is how all unsigned integers can be returned
Float() float64 Returns the value as a double (float64) type. All floating-point numbers (float32, float64) can be returned in this way
Bool() bool Returns the value as bool
Bytes() []bytes Returns the value as a byte array [] bytes
String() string Returns the value as a string

Get value by reflection

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	k := v.Kind()
	switch k {
	case reflect.Int64:
		// v.Int() gets the original value of the integer from the reflection, and then casts the type through int64()
		fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
	case reflect.Float32:
		// v.Float() gets the original value of the floating-point type from the reflection, and then casts the type through float32()
		fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
	case reflect.Float64:
		// v.Float() gets the original value of the floating-point type from the reflection, and then casts the type through float64()
		fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
	}
}
func main() {
	var a float32 = 3.14
	var b int64 = 100
	reflectValue(a) // type is float32, value is 3.140000
	reflectValue(b) // type is int64, value is 100
	// Converts the original value of type int to type reflect.Value
	c := reflect.ValueOf(10)
	fmt.Printf("type c :%T\n", c) // type c :reflect.Value
} 

Set the value of the variable by reflection

If you want to modify the value of a variable through reflection in a function, you need to note that the function parameters pass a copy of the value. You must pass the variable address to modify the variable value. In reflection, a proprietary Elem() method is used to obtain the value corresponding to the pointer.

package main

import (
	"fmt"
	"reflect"
)

func reflectSetValue1(x interface{}) {
	v := reflect.ValueOf(x)
	if v.Kind() == reflect.Int64 {
		v.SetInt(200) //If the copy is modified, the reflect package will cause panic
	}
}
func reflectSetValue2(x interface{}) {
	v := reflect.ValueOf(x)
	// In reflection, use the Elem() method to get the value corresponding to the pointer
	if v.Elem().Kind() == reflect.Int64 {
		v.Elem().SetInt(200)
	}
}
func main() {
	var a int64 = 100
	// reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value
	reflectSetValue2(&a)
	fmt.Println(a)
} 

isNil() and isValid()

isNil()

func (v Value) IsNil() bool 

IsNil() reports whether the value held by v is nil. The classification of the value held by v must be one of channels, functions, interfaces, mappings, pointers and slices; Otherwise, the IsNil function will cause panic.

isValid()

func (v Value) IsValid() bool 

IsValid() returns whether v holds a Value. If v is Value, zero Value will return false. At this time, v methods other than IsValid, String and Kind will cause panic.

for instance

IsNil() is often used to determine whether the pointer is null; IsValid() is often used to determine whether the return value is valid.

func main() {
	// *Null pointer of type int
	var a *int
	fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
	// nil value
	fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
	// Instantiate an anonymous structure
	b := struct{}{}
	// Try to find the "abc" field from the structure
	fmt.Println("Nonexistent structure member:", reflect.ValueOf(b).FieldByName("abc").IsValid())
	// Try to find the "abc" method from the structure
	fmt.Println("Nonexistent structure method:", reflect.ValueOf(b).MethodByName("abc").IsValid())
	// map
	c := map[string]int{}
	// Try to find a key that does not exist in the map
	fmt.Println("map Key not present in:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("Naza")).IsValid())
} 

Structural reflection

Methods related to structures

After any value obtains the reflection object information through reflect.TypeOf(), if its type is a structure, you can obtain the detailed information of the structure member through the NumField() and Field() methods of the reflection value object (reflect.Type).

The methods related to obtaining structure members in reflect.Type are shown in the following table.

method explain
Field(i int) StructField According to the index, return the information of the structure field corresponding to the index.
NumField() int Returns the number of structure member fields.
FieldByName(name string) (StructField, bool) Returns the information of the structure field corresponding to the given string.
FieldByIndex(index []int) StructField When accessing multi-layer members, the field information is returned according to the field index of each structure provided by [] int.
FieldByNameFunc(match func(string) bool) (StructField,bool) Match the required fields according to the passed in matching function.
NumMethod() int Returns the number of methods in the method set of this type
Method(int) Method Returns the ith method in the type method set
MethodByName(string)(Method, bool) Returns the method in the method set of this type according to the method name

StructField type

The StructField type is used to describe the information of a field in the structure.

StructField is defined as follows:

type StructField struct {
    // Name is the name of the field. PkgPath is the package path of non export field, which is' 'for export field.
    // See http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string
    Type      Type      // Type of field
    Tag       StructTag // Label of the field
    Offset    uintptr   // The byte offset of the field in the structure
    Index     []int     // Index slice for Type.FieldByIndex
    Anonymous bool      // Anonymous field
} 

Structure reflection example

When we use reflection to get a structure data, we can get its field information in turn through the index, or we can get the specified field information through the field name.

type student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func main() {
	stu1 := student{
		Name:  "princeling",
		Score: 90,
	}

	t := reflect.TypeOf(stu1)
	fmt.Println(t.Name(), t.Kind()) // student struct
	// Traverse all field information of the structure through the for loop
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
	}

	// Obtain the field information of the specified structure through the field name
	if scoreField, ok := t.FieldByName("Score"); ok {
		fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
	}
} 

Next, write a function printMethod(s interface {}) to traverse the methods contained in printing s.

// Add two methods to student, Study and sleep (note the initial capitalization)
func (s student) Study() string {
	msg := "study hard and make progress every day."
	fmt.Println(msg)
	return msg
}

func (s student) Sleep() string {
	msg := "Sleep well and grow up quickly."
	fmt.Println(msg)
	return msg
}

func printMethod(x interface{}) {
	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x)

	fmt.Println(t.NumMethod())
	for i := 0; i < v.NumMethod(); i++ {
		methodType := v.Method(i).Type()
		fmt.Printf("method name:%s\n", t.Method(i).Name)
		fmt.Printf("method:%s\n", methodType)
		// The parameter passed through a reflection call method must be of type [] reflect.Value
		var args = []reflect.Value{}
		v.Method(i).Call(args)
	}
} 

Reflection is a double-edged sword

Reflection is a powerful and expressive tool that allows us to write more flexible code. But reflection should not be abused for three reasons.

  1. Reflection based code is extremely fragile. Type errors in reflection will cause panic when it is actually running, which is likely to be a long time after the code is written.
  2. Code that uses reflection heavily is often difficult to understand.
  3. The performance of reflection is low. The code based on reflection usually runs one or two orders of magnitude slower than normal code.

Posted by sd9sd on Sat, 06 Nov 2021 19:39:52 -0700