2013-01-02 4 views
10

私はGoで書かれたテンプレートシステムで作業しています。つまり、reflectパッケージを自由に使用する必要があります。この特定の状況では、私はinterface{}でメソッドを動的に呼び出すことができる必要があります。奇妙なのは、データが既知の型のデータであれば、私のリフレクションロジックは正常に動作しますが、データの型がinterface{}ではないことです。受信者の種類に関係なく、インタフェース{}のメソッドを動的に呼び出す

次の例では、main()Pass()のロジックが同一であることがわかります。 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{}

内部の既知のタイプまたは既知のタイプであるかどうかでありますオブジェクトが現在interface{}にある場合を除いて、メソッドを動的に呼び出すための私の方法論がうまく動作することを意味します。

代わりに私はポインタ受信機を使用せず、iを渡すと、期待通りに動作します。

Goは再生:http://play.golang.org/p/myM0UXVYzX

をこれが私の問題は、それがinterface{}あるとき、私は私(&i)のアドレスにアクセスすることができないということであると信じて私をリードしています。私は反映パッケージを精査して、reflect.Value.Addr()reflect.PtrTo()のようなものをテストしましたが、私が必要な方法で作業することができませんでした。私の感想は、それはinterface{}が参照オブジェクトであるという事実と関係があるということです。

答えて

16

おかげで、私は私の問題を解決することができたと考えています。基本的な問題は、interface{}で動的に名前を付けたメソッドを呼び出すことです。 4つのケースがあります。

  1. interface{}基礎となるデータである値と受信値
  2. interface{}基礎となるデータがあるポインタと受信側の値になる
  3. interface{}基礎となるデータの値と、受信機は、ポインタ
  4. interface{}される基礎となるデータは、ポインタであり、受信機がありますポインタ

私たちはintの基礎となる価値を判断できますerface。さらにリフレクションを使用すると、現在のタイプの代替データ型を生成できます。渡されたデータは、値だった場合、我々は、我々は単にで

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

そのため、既存の方法を確認するために、それぞれを使用することができ、両方のデータ型を持っていることを今

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 
} 

へのポインタを生成する必要がありますこれを念頭に置いて、*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")) 
} 
+0

ありがとうございます。私の質問は異なっていましたが、あなたの答えは、構造体に 'IsValid()'メソッドがあるかどうか判断するのに役立ちました。 – Matt

+0

私自身の質問に答えるときに私はこれを考え出しました(http://play.golang.org/p/v7lC0swB3s)(http://stackoverflow.com/questions/20167935/can-embedded-methods-access-parent -fields) – Brenden

4

あなたの例では、FinishメソッドはTest構造体へのポインタに対してのみ定義されているので、Finishメソッドをサポートするものではpassを呼び出さない。 MethodByNameは、その場合に必要なものを正確に実行しています。 *Test != Test彼らは完全に2つの異なるタイプです。反射がなくても、テストは*テストになります。そして本当にそれはどちらかではありません。 PtrTo関数を使用して、Finishメソッドがポインタ型で定義されているかどうかを調べることができますが、実際の値へのポインタを取得するのには役立ちません。ポインタで

呼び出すパスは動作します:@Jeremyウォールへhttp://play.golang.org/p/fICI3cqT4t

+1

あなたの答えは見つけるのに非常に役立ちました問題への答え重要なのは「反省の量はテストを*テストに変えるものではない」ということでした。それは、問題を解決することが、それを正確に行う方法を見つけることに頼っていることに気づきました。私は自分の答えを追加しました。私が信じているのは、リフレクションを使って 'Test'から' * Test'を作成できることを証明しています。これにより、受信者のタイプにかかわらず、任意の 'interface {}'のメソッドを名前で動的に呼び出すことができます。 – Nucleon

+0

実際には、リフレクションAPIを使用して、値へのポインタである新しい型を作成することができます。しかし、あなたの下で動作する哲学の下では、あなたが渡したのと同じタイプでもはや働いていません。いずれにせよ、あなたが問題を解決することができてうれしいです。 –

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

関連する問題