我正在使用Go编写的模板系统,这意味着它需要自由使用该reflect软件包。在这种特定情况下,我需要能够在上动态调用方法interface{}。奇怪的是,只要我的数据是已知类型,反射逻辑就可以正常工作,但是如果数据是type则不能interface{}。
reflect
interface{}
在下面的例子中可以看到,在逻辑main()和Pass()是相同的。唯一的区别是数据是内部的已知类型还是已知类型interface{}
main()
Pass()
播放:http://play.golang.org/p/FTP3wgc0sZ
package main import ( "fmt" "reflect" ) type Test struct { Start string } func (t *Test) Finish() string { return t.Start + "finish" } func Pass(i interface{}) { _, ok := reflect.TypeOf(&i).MethodByName("Finish") if ok { fmt.Println(reflect.ValueOf(&i).MethodByName("Finish").Call([]reflect.Value{})[0]) } else { fmt.Println("Pass() fail") } } func main() { i := Test{Start: "start"} Pass(i) _, ok := reflect.TypeOf(&i).MethodByName("Finish") if ok { fmt.Println(reflect.ValueOf(&i).MethodByName("Finish").Call([]reflect.Value{})[0]) } else { fmt.Println("main() fail") } }
执行此代码后,我们得到以下结果
Pass() fail startfinish
这意味着我的动态调用方法的方法可以正常工作,除非在我的对象当前位于的情况下interface{}。
相反,如果我不使用指针接收器并通过,i那么它将按预期工作。
i
播放:http://play.golang.org/p/myM0UXVYzX
这使我相信我的问题是,&i当i()是时,我无法访问其地址interface{}。我已经检查了反射包并测试了诸如和的东西reflect.Value.Addr(),reflect.PtrTo()但是我都无法按照我需要的方式工作。我的直觉是,这与以下事实有关interface{}:根据定义,an 是参考对象。
&i
reflect.Value.Addr()
reflect.PtrTo()
感谢@Jeremy Wall,我相信我能够解决我的问题。基本问题是在上调用动态命名方法interface{}。有4种情况。
使用反射,我们可以确定接口的基础值。然后,使用进一步的反射,我们可以生成当前数据类型的替代数据类型。如果传入的数据是一个值,我们需要生成一个指向它的指针
value := reflect.ValueOf(data) if value.Type().Kind() == reflect.Ptr { ptr = value value = ptr.Elem() // acquire value referenced by pointer } else { ptr = reflect.New(reflect.TypeOf(i)) // create new pointer temp := ptr.Elem() // create variable to value of pointer temp.Set(value) // set value of variable to our passed in value }
现在我们有了两种数据类型,我们可以简单地使用每种数据类型来检查现有方法
var finalMethod reflect.Value method := value.MethodByName(methodName) if method.IsValid() { finalMethod = method } // check for method on pointer method = ptr.MethodByName(methodName) if method.IsValid() { finalMethod = method } if (finalMethod.IsValid()) { return finalMethod.Call([]reflect.Value{})[0].String() }
因此,考虑到这一点,我们可以有效地动态调用任何方法,无论是声明为*receiver还是receiver。
*receiver
receiver
完整的概念证明:http://play.golang.org/p/AU- Km5VjZs
package main import ( "fmt" "reflect" ) type Test struct { Start string } // value receiver func (t Test) Finish() string { return t.Start + "finish" } // pointer receiver func (t *Test) Another() string { return t.Start + "another" } func CallMethod(i interface{}, methodName string) interface{} { var ptr reflect.Value var value reflect.Value var finalMethod reflect.Value value = reflect.ValueOf(i) // if we start with a pointer, we need to get value pointed to // if we start with a value, we need to get a pointer to that value if value.Type().Kind() == reflect.Ptr { ptr = value value = ptr.Elem() } else { ptr = reflect.New(reflect.TypeOf(i)) temp := ptr.Elem() temp.Set(value) } // check for method on value method := value.MethodByName(methodName) if method.IsValid() { finalMethod = method } // check for method on pointer method = ptr.MethodByName(methodName) if method.IsValid() { finalMethod = method } if (finalMethod.IsValid()) { return finalMethod.Call([]reflect.Value{})[0].Interface() } // return or panic, method not found of either type return "" } func main() { i := Test{Start: "start"} j := Test{Start: "start2"} fmt.Println(CallMethod(i, "Finish")) fmt.Println(CallMethod(&i, "Finish")) fmt.Println(CallMethod(i, "Another")) fmt.Println(CallMethod(&i, "Another")) fmt.Println(CallMethod(j, "Finish")) fmt.Println(CallMethod(&j, "Finish")) fmt.Println(CallMethod(j, "Another")) fmt.Println(CallMethod(&j, "Another")) }