17

私は Go で書かれたテンプレート システムに取り組んでいます。つまり、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( &i)のアドレスにアクセスできないことだと思いますinterface{}。Reflect パッケージを精査し、 and などをテストしましたreflect.Value.Addr()reflect.PtrTo()、どちらも必要な方法で動作させることができませんでした。interface{}私の推測では、 anが定義上参照オブジェクトであるという事実と関係があると思います。

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 は Test 構造体へのポインターでのみ定義されているため、Finish メソッドをサポートするもので pass を呼び出しません。MethodByName は、その場合にすべきことを正確に行っています。*Test != Testそれらは2つの完全に異なるタイプです。どの程度のリフレクションでも 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 に答える