Dynamically call method on interface{} regardless of receiver type

前端 未结 3 1714
眼角桃花
眼角桃花 2020-12-13 03:03

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 t

相关标签:
3条回答
  • 2020-12-13 03:27

    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

    0 讨论(0)
  • 2020-12-13 03:37

    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"))
    }
    
    0 讨论(0)
  • 2020-12-13 03:41

    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

    0 讨论(0)
提交回复
热议问题