我正在开发一个用Go编写的模板系统,这意味着它需要自由使用reflect包.在这种特定情况下,我需要能够在interface{}上动态调用一个方法.奇怪的是,只要我的数据是已知类型,我的反射逻辑就可以正常工作,但如果数据是interface{}类型,就不行.

在下面的示例中,您可以看到main()Pass()中的逻辑是相同的.唯一的区别是数据是已知类型还是interface{}内部的已知类型

go 玩吧: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,那么它会按预期工作.

go 玩吧:http://play.golang.org/p/myM0UXVYzX

这让我相信,我的问题是,当I(&i)是interface{}时,我无法访问它的地址.我已经搜索了reflect软件包并测试了reflect.Value.Addr()reflect.PtrTo()之类的东西,但我都无法按照我需要的方式工作.我的直觉是,它与一个事实有关,根据定义,interface{}是一个参考对象.

推荐答案

多亏了@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"))
}

Go相关问答推荐

区分Terminal和Hook Zerolog Go中的错误级别日志(log)输出

如何修复在Go API中使用Gin Framework的请求资源上没有使用Gin Framework的请求源的消息?''

Go-Colly:将数据切片为POST请求

允许在 struct 中使用复合作为函数参数

在GO中使用泛型类型 struct 实现接口方法

重新赋值变量时未清除动态类型-这是错误吗?

迭代字符串并用映射值替换原始字符串中的值的惯用方法

nixOS 上的 Nginx 反向代理在try 加载 css/js 时返回 404

如何在 Go msgraph-sdk-go 中转发消息并包括抄送和/或密送收件人?

go - 仅在函数即将返回错误时清理资源

如何使用带有方法的字符串枚举作为通用参数?

仅在工作日运行 cron

如何在自定义验证函数中获取 struct 名称

如何在 golang 中同时加载 .env 文件和 os 环境变量

如何在gin中获取参数值数组

使用 Go 读取 TOML 文件时结果为空

Go Fyne 禁用 HSplit 调整大小?

Terraform 自定义提供程序 - 数据源架构

如何在 Unmarshal 中使用泛型(转到 1.18)

go mod tidy 错误消息:但是 go 1.16 会 Select