Go反射四讲---第三讲:如何使用反射操作函数,获取函数的相关信息?

反射-函数

这是我们反射四讲的第三讲,本次给大家讲解如何使用反射处理函数相关的操作。

在这一部分,向大家展示如何输出方法的信息并执行调用。

输出信息,包含方法名,方法参数,返回值。

最后,如何使用反射调用函数。

提醒:在实现时候,我们要非常注意指针的使用,不然会出现 Bug。

测试函数

type User struct {
	Name string
	Age  int
}

func NewUserPoint(name string, age int) *User {
	return &User{
		Name: name,
		Age:  age,
	}
}

func NewUser(name string, age int) User {
	return User{
		Name: name,
		Age:  age,
	}
}

func (u User) GetAge() int {
	return u.Age
}

func (u *User) ChangeName(NewName string) {
	u.Name = NewName
}

func (u *User) private() {
	fmt.Println("private")
}

定义元数据

type FuncInfo struct {
	Name   string         // 方法名
	In     []reflect.Type // 方法输入参数
	Out    []reflect.Type // 方法输出参数
	Result []any          
}

使用调用

// CallMethod 输出方法的信息并执行调用, 输出:方法名,方法参数,返回值
func CallMethod(val any) (map[string]*FuncInfo, error) {
	typ := reflect.TypeOf(val)
	if typ.Kind() != reflect.Struct && typ.Kind() != reflect.Ptr {
		return nil, errors.New("非法类型")
	}
	// 构建结果集
	num := typ.NumMethod()
	result := make(map[string]*FuncInfo, num)
	for i := 0; i < num; i++ {
		fc := typ.Method(i)
		// 输入
		numIn := fc.Type.NumIn()
		ps := make([]reflect.Value, 0, fc.Type.NumIn())
		// 第一个参数永远是接收者,
		ps = append(ps, reflect.ValueOf(val))

		in := make([]reflect.Type, 0, fc.Type.NumIn())
		for j := 0; j < numIn; j++ {
			p := fc.Type.In(j)
			in = append(in, p)
			if j > 0 {
				ps = append(ps, reflect.Zero(p))
			}
		}

		// 调用方法
		ret := fc.Func.Call(ps)

		// 输出
		outNum := fc.Type.NumOut()
		out := make([]reflect.Type, 0, outNum)
		res := make([]any, 0, outNum)
		for k := 0; k < outNum; k++ {
			out = append(out, fc.Type.Out(k))
			res = append(res, ret[k].Interface())
		}
		
		result[fc.Name] = &FuncInfo{
			Name:   fc.Name,
			In:     in,
			Out:    out,
			Result: res,
		}
	}
	return result, nil
}

测试:

func TestCallMethod(t *testing.T) {
	testcases := []struct {
		name    string
		input   any
		wantRes map[string]*FuncInfo
		wantErr error
	}{
		{
			name:  "normal struct",
			input: types.User{},
			wantRes: map[string]*FuncInfo{
				"GetAge": {
					Name:   "GetAge",
					In:     []reflect.Type{reflect.TypeOf(types.User{})},
					Out:    []reflect.Type{reflect.TypeOf(0)},
					Result: []any{0},
				},
			},
		},
		{
			// 指针
			name:  "pointer",
			input: &types.User{},
			wantRes: map[string]*FuncInfo{
				"GetAge": {
					Name:   "GetAge",
					In:     []reflect.Type{reflect.TypeOf(&types.User{})},
					Out:    []reflect.Type{reflect.TypeOf(0)},
					Result: []any{0},
				},
				"ChangeName": {
					Name:   "ChangeName",
					In:     []reflect.Type{reflect.TypeOf(&types.User{}), reflect.TypeOf("")},
					Out:    []reflect.Type{},
					Result: []any{},
				},
			},
		},
	}

	for _, tt := range testcases {
		t.Run(tt.name, func(t *testing.T) {
			res, err := CallMethod(tt.input)
			assert.Equal(t, tt.wantErr, err)
			if err != nil {
				return
			}
			assert.Equal(t, tt.wantRes, res)
		})
	}
}

总结

对于方法接收器:

  • 以结构体作为输入,那么只能访问到结构体作为接收器的方法
  • 以指针作为输入,那么能访问到任何接收器的方法

输入的第一个参数,永远都是接收器本身。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值