17

我正在开发一个用 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()但是我无法按照我需要的方式工作。我的预感是它与 aninterface{}定义为引用对象这一事实有关。

4

3 回答 3

26

感谢@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"))
}
于 2013-01-04T17:46:27.300 回答
5

在您的示例中,您不会使用支持 Finish 方法的东西调用 pass,因为 Finish 仅在指向 Test 结构的指针上定义。MethodByName 在这种情况下正在做它应该做的事情。*Test != Test它们完全是两种不同的类型。再多的反思也不会将测试变成*测试。实际上也不应该。您可以使用 PtrTo 函数来确定 Finish 方法是否在指针类型上定义,但这无助于您获得指向实际值的指针。

使用指针调用 Pass 有效:http ://play.golang.org/p/fICI3cqT4t

于 2013-01-02T06:03:25.083 回答
0

这是 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

于 2016-12-08T08:22:53.530 回答