英文:
Dynamically call method on interface{} regardless of receiver type
问题
我正在使用Go语言编写一个模板系统,这意味着它需要大量使用reflect
包。在这种特定情况下,我需要能够动态调用interface{}
上的方法。奇怪的是,我的反射逻辑只在数据是已知类型时才能正常工作,而对于类型为interface{}
的数据则不行。
在下面的示例中,您可以看到main()
和Pass()
中的逻辑是相同的。唯一的区别是数据是已知类型还是已知类型在interface{}
中。
Go Play链接: 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
,则结果符合预期。
Go Play链接: http://play.golang.org/p/myM0UXVYzX
这让我相信,我的问题在于当i
为interface{}
时无法访问其地址(&i
)。我已经仔细查阅了reflect包,并测试了reflect.Value.Addr()
和reflect.PtrTo()
等方法,但都无法按照我需要的方式工作。我猜测问题可能与interface{}
本质上是引用对象有关。
英文:
I'm working on a templating system written in Go, which means it requires liberal use of the reflect
package. In this specific circumstance I need to be able to dynamically call a method on an interface{}
. The oddity is that my reflection logic works fine as long as my data is of a known type, but not if the data is of type interface{}
.
The the following example you can see that the logic in main()
and Pass()
is identical. The only difference is whether the data is a known type or a known type inside an interface{}
Go Play: 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")
}
}
Upon executing this code we get the following result
Pass() fail
startfinish
Which means that my methodology for dynamically calling a method works fine except in a scenario when my object is currently in an interface{}
.
If instead I do not use a pointer receiver and pass i
then it works as expected.
Go Play: http://play.golang.org/p/myM0UXVYzX
This leads me to believe that my problem is that I cannot access the address of i (&i
) when it is an interface{}
. I've scoured the reflect package and tested things such as reflect.Value.Addr()
and reflect.PtrTo()
but I could not get either to work the way I needed. My hunch is that it has something to do with the fact that an interface{}
is by definition a reference object.
答案1
得分: 27
感谢@Jeremy Wall,我相信我能够解决我的问题。基本问题是在interface{}
上调用一个动态命名的方法。有4种情况。
interface{}
底层数据是值,接收器是值interface{}
底层数据是指针,接收器是值interface{}
底层数据是值,接收器是指针interface{}
底层数据是指针,接收器是指针
使用反射,我们可以确定接口的底层值。然后使用进一步的反射,我们可以生成当前类型的替代数据类型。如果传入的数据是值,我们需要生成一个指向它的指针。
value := reflect.ValueOf(data)
if value.Type().Kind() == reflect.Ptr {
ptr = value
value = ptr.Elem() // 获取指针引用的值
} else {
ptr = reflect.New(reflect.TypeOf(i)) // 创建新的指针
temp := ptr.Elem() // 创建变量以存储指针的值
temp.Set(value) // 将变量的值设置为传入的值
}
现在我们有了两种数据类型,我们可以使用每种类型来检查是否存在方法。
var finalMethod reflect.Value
method := value.MethodByName(methodName)
if method.IsValid() {
finalMethod = method
}
// 检查指针上的方法
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
}
// 值接收器
func (t Test) Finish() string {
return t.Start + "finish"
}
// 指针接收器
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 value.Type().Kind() == reflect.Ptr {
ptr = value
value = ptr.Elem()
} else {
ptr = reflect.New(reflect.TypeOf(i))
temp := ptr.Elem()
temp.Set(value)
}
// 检查值上的方法
method := value.MethodByName(methodName)
if method.IsValid() {
finalMethod = method
}
// 检查指针上的方法
method = ptr.MethodByName(methodName)
if method.IsValid() {
finalMethod = method
}
if (finalMethod.IsValid()) {
return finalMethod.Call([]reflect.Value{})[0].Interface()
}
// 返回或抛出异常,找不到任何类型的方法
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"))
}
英文:
Thanks to @Jeremy Wall I believe I was able to solve my problem. The basic issue is calling a dynamically named method on an interface{}
. There are 4 cases.
interface{}
underlying data is value and receiver is valueinterface{}
underlying data is pointer and receiver is valueinterface{}
underlying data is value and receiver is pointerinterface{}
underlying data is pointer and receiver is pointer
Using reflection we can determine the underling value of our interface. Then using further reflection we can generate the alternate data type to our current type. If the data passed in was a value we need to generate a pointer to it
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
}
Now that we have both data types we can simply use each to check for an existing method
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()
}
Therefore with this in mind we can effectively call any method, dynamically, whether declared as *receiver
or receiver
.
Full Proof of Concept: 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"))
}
答案2
得分: 5
在你的例子中,你没有使用支持Finish方法的东西来调用pass,因为Finish只在指向Test结构体的指针上定义。在这种情况下,MethodByName正好做了它应该做的事情。*Test != Test
它们是完全不同的两种类型。无论如何反射都不能将Test转换为*Test。而且实际上也不应该这样做。你可以使用PtrTo函数来查找指针类型上是否定义了Finish方法,但这并不能帮助你获得指向实际值的指针。
使用指针调用Pass可以正常工作:http://play.golang.org/p/fICI3cqT4t
英文:
In your example you don't call pass with something that supports the Finish method since Finish is only defined on pointers to Test structs. MethodByName is doing exactly what it should in that case. *Test != Test
they are two different types completely. No amount of reflection will turn a Test into a *Test. And really it shouldn't either. You can use the PtrTo function to find out if the Finish method is defined on the pointer type but that won't help you get a pointer to the actual value.
Calling Pass with a pointer works: http://play.golang.org/p/fICI3cqT4t
答案3
得分: 0
这是一个使用golang实现类似功能的好例子:
package main
import "fmt"
type Parent struct {
Attr1 string
}
type Parenter interface {
GetParent() Parent
}
type Child struct {
Parent //嵌入
Attr string
}
func (c Child) GetParent() Parent {
return c.Parent
}
func setf(p Parenter) {
fmt.Println(p)
}
func main() {
var ch Child
ch.Attr = "1"
ch.Attr1 = "2"
setf(ch)
}
代码来源:https://groups.google.com/d/msg/golang-nuts/8lK0WsGqQ-0/HJgsYW_HCDoJ
英文:
This is a good example of golang to implement similar functionality:
package main
import "fmt"
type Parent struct {
Attr1 string
}
type Parenter interface {
GetParent() Parent
}
type Child struct {
Parent //embed
Attr string
}
func (c Child) GetParent() Parent {
return c.Parent
}
func setf(p Parenter) {
fmt.Println(p)
}
func main() {
var ch Child
ch.Attr = "1"
ch.Attr1 = "2"
setf(ch)
}
code from : https://groups.google.com/d/msg/golang-nuts/8lK0WsGqQ-0/HJgsYW_HCDoJ
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论