• 【Golang | reflect】利用反射实现方法的调用


    引言

    go语言中,如果某个数据类型实现了一系列的方法,如何批量去执行呢,这时候就可以利用反射里的func (v Value) Call(in []Value) []Value 方法。

    // Call calls the function v with the input arguments in.
    // For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]).
    // Call panics if v's Kind is not Func.
    // It returns the output results as Values.
    // As in Go, each input argument must be assignable to the
    // type of the function's corresponding input parameter.
    // If v is a variadic function, Call creates the variadic slice parameter
    // itself, copying in the corresponding values.
    func (v Value) Call(in []Value) []Value {
    	v.mustBe(Func)
    	v.mustBeExported()
    	return v.call("Call", in)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Call方法实际使用时主要有以下两种调用方式:

    方法1:使用reflect.Type.Method.Func.Call

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type dog struct {
    	name string
    }
    
    func (a *dog) Speak() {
    	fmt.Println("speaking!")
    }
    func (a *dog) Walk() {
    	fmt.Println("walking!")
    }
    
    func (a *dog) GetName() (string, error) {
    	fmt.Println("Getting name!")
    	return a.name, nil
    }
    func main() {
    	var tudou = &dog{name: "tudou"}
    	// 获取reflect.Type
    	objectType := reflect.TypeOf(tudou)
    	// 批量执行方法
    	for i := 0; i < objectType.NumMethod(); i++ {
    		fmt.Printf("Now method: %v is being executed...\n", objectType.Method(i).Name)
    		// Call的第一个入参对应receiver,即方法的接受者本身
    		ret := objectType.Method(i).Func.Call([]reflect.Value{reflect.ValueOf(tudou)})
    		fmt.Printf("The return of method: %s is %v\n\n", objectType.Method(i).Name, ret)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    注:
    1、这里说明下,为什么使用Func调用Call时第一个入参是对应receiver本身method.Func.Call([]reflect.Value{reflect.ValueOf(tudou)})
    可以看下结构体MethodFunc的定义,有这么一句注释func with receiver as first argument

    /*
     * The compiler knows the exact layout of all the data structures above.
     * The compiler does not know about the data structures and methods below.
     */
    
    // Method represents a single method.
    type Method struct {
    	...
    	Func  Value // func with receiver as first argument
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、objectType.Method(i)返回的是一个Method结构体

    方法2:使用reflect.Value.Method(index).Call

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type dog struct {
    	name string
    }
    
    func (a *dog) Speak() {
    	fmt.Println("speaking!")
    }
    
    func (a *dog) SetName(name string) error {
    	fmt.Println("Setting name!")
    	a.name = name
    	return nil
    }
    
    func (a *dog) GetName() (string, error) {
    	fmt.Println("Getting name!")
    	return a.name, nil
    }
    
    func main() {
    	var tudou = &dog{name: "tudou"}
    	// 获取reflect.Value
    	objectValue := reflect.ValueOf(tudou)
    	// 根据方法名获取method,执行Call
    	objectValue.MethodByName("Speak").Call(nil)
    	objectValue.MethodByName("SetName").Call([]reflect.Value{reflect.ValueOf("newName")})
    	objectValue.MethodByName("GetName").Call(nil)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    注:
    1、不同于方法1,使用reflect.Value.Method直接调用Call,不需要使用receiver作为第一个入参。可以看下方法MethodByName的注释,有这么一句The arguments to a Call on the returned function should not include a receiver

    // MethodByName returns a function value corresponding to the method
    // of v with the given name.
    // The arguments to a Call on the returned function should not include
    // a receiver; the returned function will always use v as the receiver.
    // It returns the zero Value if no method was found.
    func (v Value) MethodByName(name string) Value {
    	if v.typ == nil {
    		panic(&ValueError{"reflect.Value.MethodByName", Invalid})
    	}
    	if v.flag&flagMethod != 0 {
    		return Value{}
    	}
    	m, ok := v.typ.MethodByName(name)
    	if !ok {
    		return Value{}
    	}
    	return v.Method(m.Index)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2、objectValue.MethodByName("Speak")返回的是一个reflect.Value,这个跟方法1调用Method()有明显区别

    3、另外值得留意的是,虽然方法2可以参考方法1的for循环批量执行method,但是reflect.Value似乎并没有直接提供方法获取每一个method的Name。但是我们可以根据Index借助reflect.Type.Method(Index).Name来获取Name,这是因为每一个method的NameIndex是一一对应的

    type Method struct {
    	// Name is the method name.
    	Name string
    	...
    	Index int   // index for Type.Method
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其实,注1里的方法func (v Value) MethodByName(name string) Value 里有一段也是根据这个对应关系实现的m, ok := v.typ.MethodByName(name) ... return v.Method(m.Index),有兴趣的同学可以留意观察下

  • 相关阅读:
    IDEA常用快捷键,提升开发效率
    表白墙程序
    CANoe-以太网Link up问题、如何打开TC8参数配置文件
    【软考】-- 操作系统(中)
    Doris实战——美联物业数仓
    力扣刷题记录103.1-----518. 零钱兑换 II
    【读博日记】拓扑结构(待修正)
    深入理解element-wise add与concat特征图的连接方式
    MVC第三波书店缓存帮助类RedisHelper
    【Codeforces】 CF1685C Bring Balance
  • 原文地址:https://blog.csdn.net/weixin_42216109/article/details/133966426