动态调用方法上接口{}而不管接收器类型的(Dynamically call method on in

2019-07-17 18:02发布

我正在写在围棋模板系统,这意味着它需要自由的使用上reflect包。 在这种具体情况我需要能够动态调用的方法的上interface{} 古怪的是,我的反射逻辑工作正常,只要我的数据是已知类型的,但如果该数据是类型的interface{}

在下面的例子中可以看到,在逻辑main()Pass()是相同的。 唯一的区别是该数据是否是一已知类型或已知类型的内部interface{}

去发挥: 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那么它将按预期工作。

去发挥: http://play.golang.org/p/myM0UXVYzX

这使我相信,我的问题是,我不能访问我(的地址&i当它是一个) interface{} 我已经冲刷了反映封装和测试的东西,如reflect.Value.Addr()reflect.PtrTo()但我无法得到任何工作,我需要的方式。 我的直觉是,它是与事实,即一个interface{}是定义参考对象。

Answer 1:

由于@Jeremy长城,我相信我能解决我的问题。 基本问题是一个在调用动态命名方法interface{} 有4例。

  1. interface{}基本数据是值和接收器是值
  2. interface{}基本数据是指针和接收器是值
  3. interface{}基本数据是值和接收器是指针
  4. interface{}基本数据是指针和接收器是指针

使用反射,我们可以判断我们的接口的下属值。 然后用另外的反射,可以生成替代数据类型,我们目前的类型。 如果传入的数据是一个值,我们需要生成一个指向它

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
}

现在,我们有两种数据类型,我们可以简单地使用各检查现有的方法

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

因此,考虑到这一点,我们可以有效地调用任何方法,动态,无论是声明为*receiverreceiver

理念的充分证明: 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"))
}


Answer 2:

在你的榜样,你不叫传与支持,因为完成仅在指针定义为测试结构的完成方法的东西。 MethodByName究竟是做什么它应该在这种情况下。 *Test != Test它们是两个不同类型的完全。 反射再多会变成一个测试成*测试。 说实在也不应该。 您可以使用PtrTo功能,以找出是否Finish方法是在指针类型定义,但不会帮助你获得一个指向实际值。

用指针调用工作通行证: http://play.golang.org/p/fICI3cqT4t



Answer 3:

这是golang实现类似功能的一个很好的例子:

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

代码: https://groups.google.com/d/msg/golang-nuts/8lK0WsGqQ-0/HJgsYW_HCDoJ



文章来源: Dynamically call method on interface{} regardless of receiver type