我正在开发一个用Go编写的模板系统,这意味着它需要自由使用reflect
包。在这种特定情况下,我需要能够在interface{}
上动态调用方法。奇怪的是,只要我的数据属于已知类型,我的反射逻辑就可以正常工作,但如果数据类型为interface{}
则不行。
以下示例您可以看到main()
和Pass()
中的逻辑相同。唯一的区别是数据是interface{}
去玩: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
,那么它按预期工作。
去玩:http://play.golang.org/p/myM0UXVYzX
这让我相信我的问题是,当&i
为interface{}
时,我无法访问i(reflect.Value.Addr()
)的地址。我已经搜索了反射包并测试了诸如reflect.PtrTo()
和interface{}
之类的东西,但我无法以我需要的方式工作。我的预感是它与{{1}}定义为参考对象这一事实有关。
答案 0 :(得分:18)
感谢@Jeremy Wall我相信我能够解决我的问题。基本问题是在interface{}
上调用动态命名的方法。有4个案例。
interface{}
基础数据是值,接收者是值interface{}
基础数据是指针,接收者是值interface{}
基础数据是值,接收者是指针interface{}
底层数据是指针,接收器是指针使用反射我们可以确定界面的下划线值。然后使用进一步反射,我们可以生成当前类型的备用数据类型。如果传入的数据是一个值,我们需要生成一个指向它的指针
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
。
完整的概念证明: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"))
}
答案 1 :(得分:4)
在您的示例中,您不会使用支持Finish方法的内容调用pass,因为Finish仅在指向Test structs的指针上定义。在这种情况下,MethodByName正在做它应该做的事情。 *Test != Test
他们完全是两种不同的类型。没有多少反射会将测试变成*测试。而且它也不应该。您可以使用PtrTo函数来确定是否在指针类型上定义了Finish方法,但这不会帮助您获得指向实际值的指针。
使用指针调用Pass:http://play.golang.org/p/fICI3cqT4t