动态调用接口{}上的方法,而不管接收者的类型如何 [英] Dynamically call method on interface{} regardless of receiver type

查看:156
本文介绍了动态调用接口{}上的方法,而不管接收者的类型如何的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究用Go编写的模板系统,这意味着它需要广泛使用 reflect 包。在这种特殊情况下,我需要能够在 interface {} 上动态调用方法。奇怪的是,只要我的数据是已知类型的数据,我的反射逻辑就能正常工作,但如果数据类型为 interface {} ,那么不是。

以下示例可以看到 main() Pass()是相同的。唯一的区别是数据在界面{}



内部是已知类型还是已知类型。 : http://play.golang.org/p/FTP3wgc0sZ

 包裹主要

进口(
fmt
反映


类型测试结构{
起始字符串
}

func(t * Test)完成()字符串{
返回t.Start +完成
}

func Pass(i interface {}){
_,ok:= reflect.TypeOf(& i).MethodByName(Finish)
if if {
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(安培;ⅰ).MethodByNam e(Finish)。Call([] reflect.Value {})[0])
} else {
fmt.Println(main()fail)
}

$ / code>

执行此代码后,我们得到以下结果:

  Pass()失败
startfinish

这意味着我的动态调用方法的方法可以正常工作,除非我的对象当前位于 interface {} 中。



如果我不使用指针接收器并传递 i ,那么它可以按预期工作。



开始游戏: http://play.golang.org/p/myM0UXVYzX



这让我相信我的问题是我无法访问我的地址(& i )它是一个界面{} 。我搜索了反射包并测试了诸如 reflect.Value.Addr() reflect.PtrTo()但我无法按照我需要的方式工作。我的直觉是,它与 interface {} 定义为引用对象有关。

解决方案

感谢@Jeremy Wall,我相信我能解决我的问题。基本问题是在接口{} 上调用一个动态命名的方法。有4种情况。


  1. 接口{} 底层数据是值,接收者是值

  2. 接口{} 基础数据是指针,接收者是值
  3. interface {} 基础数据是值,接收者是指针
  4. 接口{} 底层数据是指针而接收者是指针

使用反射我们可以确定接口的下标值。然后使用进一步的反射,我们可以生成替代数据类型到我们当前的类型。如果传入的数据是一个值,我们需要生成一个指针。

  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 
方法:= value.MethodByName(methodName)
if method.IsValid(){
finalMethod = method
}
//检查指针
method = ptr上的方法。 MethodByName(methodName)
如果method.IsValid(){
finalMethod =方法
}

if(finalMethod.IsValid()){
return finalMethod .CALL([]参考lect.Value {})[0] .String()
}

记住,我们可以动态地调用任何方法,无论是声明为 * receiver 还是 receiver



充分的概念验证: http://play.golang。 org / p / AU-Km5VjZs

 包主

导入(
fmt
反映


类型测试结构{
开始字符串
}

//值receiver
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()
}

//返回或恐慌,找不到任何类型的方法
返回
}

func main(){
i:= Test {Start:start}
j:= Test {Start:st (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)) )
}


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.

解决方案

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.

  1. interface{} underlying data is value and receiver is value
  2. interface{} underlying data is pointer and receiver is value
  3. interface{} underlying data is value and receiver is pointer
  4. interface{} 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"))
}

这篇关于动态调用接口{}上的方法,而不管接收者的类型如何的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆