必须用可寻址指针接收者、参数全转reflect.Value、方法名首字母大写;否则Call()必panic。需检查IsValid()、Kind()==Func,返回值须按索引和类型安全解包。

直接用 reflect.Value.Call 调用方法,必须传指针接收者、参数全包成 reflect.Value、方法名首字母大写——漏任一条件就 panic。
接收者必须是指针且可寻址
Go 反射要求方法调用的接收者是「可寻址」的。哪怕方法签名是 (*Client) Method1,你传 Client{} 进 reflect.ValueOf() 也会在 Call() 时崩溃:panic: reflect: call of unaddressable value。
- ✅ 正确:接收者变量本身是指针,如
c := &Client{},然后reflect.ValueOf(c) - ❌ 错误:用值类型实例,如
c := Client{},再reflect.ValueOf(&c)—— 这看似取了地址,但临时变量不可寻址,仍会 panic - ⚠️ 注意:如果结构体字段里嵌了非导出字段,
reflect.ValueOf(c).CanAddr()返回 false,也调不了;此时只能换设计,别依赖反射改私有状态
参数必须严格匹配签名并显式包装
方法定义是 func (c *Client) Do(x int, y *string) error,你就得传两个 reflect.Value:一个 reflect.ValueOf(123),一个 reflect.ValueOf(&s)(s 是 string 变量)。少一个、多一个、类型错位,Call() 都直接 panic。
- 空参调用写
method.Call(nil),别写method.Call([]reflect.Value{})—— Go 1.20+ 对空切片容忍变严,旧写法可能触发 unexpected nil slice - 传结构体指针时,确保原变量已声明,别传
reflect.ValueOf(&Struct{})这种字面量地址,它生命周期只到当前行 - 基础类型别名(如
type UserID int)和原类型不互通,需用reflect.ValueOf(u).Convert(reflect.TypeOf(int(0)).Type)强转,否则 panic: type mismatch
调用前必须检查 method.IsValid() 和 Kind
MethodByName("Foo") 找不到方法时返回零值 reflect.Value,它的 IsValid() 是 false,Kind() 是 reflect.Invalid。直接 Call() 就是经典的 panic: call of reflect.Value.Call on zero Value。
立即学习“go语言免费学习笔记(深入)”;
- 永远先判
if !method.IsValid() { return fmt.Errorf("method %s not found or not exported", name) } - 再确认
method.Kind() == reflect.Func,防止误把字段当方法(比如结构体里真有个叫Method1的字段) - 非导出方法(小写开头)永远查不到,反射不越包级可见性边界——这不是 bug,是设计
返回值要按索引安全解包
Call() 总是返回 []reflect.Value,哪怕函数没返回值,长度也是 0。硬取 results[0].Interface() 极易越界 panic。
- 先判断
len(results) > 0,再对每个元素调.IsValid() - 单返回值如
string:用results[0].String();是int就用.Int();别无脑.Interface()再断言,那多一层 runtime 开销还可能 panic - 多返回值(如
(int, error)):分别处理results[0].Int()和results[1].Interface(),且后者必须判空:if !results[1].IsNil() { err = results[1].Interface().(error) }
最常被忽略的是:跨包调用时,不仅方法要导出,接收者类型本身也得导出(type Client struct 而非 type client struct),否则 reflect.ValueOf(&c) 拿到的仍是不可导出值,MethodByName 失效。这和函数内联、编译器优化无关,是反射系统底层的访问控制逻辑。


















