Dynamically call method on interface{} regardless of receiver type

℡╲_俬逩灬. 提交于 2019-11-28 17:38:58

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"))
}

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

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!