dongle19863 2013-01-02 04:26
浏览 49
已采纳

无论接收器类型如何,接口{}上的动态调用方法

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.

  • 写回答

3条回答 默认 最新

  • dongyu2047 2013-01-04 17:46
    关注

    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"))
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

悬赏问题

  • ¥15 目前主流的音乐软件,像网易云音乐,QQ音乐他们的前端和后台部分是用的什么技术实现的?求解!
  • ¥60 pb数据库修改与连接
  • ¥15 spss统计中二分类变量和有序变量的相关性分析可以用kendall相关分析吗?
  • ¥15 拟通过pc下指令到安卓系统,如果追求响应速度,尽可能无延迟,是不是用安卓模拟器会优于实体的安卓手机?如果是,可以快多少毫秒?
  • ¥20 神经网络Sequential name=sequential, built=False
  • ¥16 Qphython 用xlrd读取excel报错
  • ¥15 单片机学习顺序问题!!
  • ¥15 ikuai客户端多拨vpn,重启总是有个别重拨不上
  • ¥20 关于#anlogic#sdram#的问题,如何解决?(关键词-performance)
  • ¥15 相敏解调 matlab