一尘不染

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

go

我正在使用Go编写的模板系统,这意味着它需要自由使用该reflect软件包。在这种特定情况下,我需要能够在上动态调用方法interface{}。奇怪的是,只要我的数据是已知类型,反射逻辑就可以正常工作,但是如果数据是type则不能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当i()是时,我无法访问其地址interface{}。我已经检查了反射包并测试了诸如和的东西reflect.Value.Addr()reflect.PtrTo()但是我都无法按照我需要的方式工作。我的直觉是,这与以下事实有关interface{}:根据定义,an
是参考对象。


阅读 175

收藏
2020-07-02

共1个答案

一尘不染

感谢@Jeremy Wall,我相信我能够解决我的问题。基本问题是在上调用动态命名方法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()
}

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

完整的概念证明: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"))
}
2020-07-02