nvcnvn
nvcnvn

Reputation: 5175

Call a Struct and its Method by name in Go?

I have found a function call MethodByName() here http://golang.org/pkg/reflect/#Value.MethodByName but it's not exactly what I want! (maybe because I don't know how to use it ... I cannot find any example with it). What I want is:

type MyStruct struct {
//some feilds here
} 
func (p *MyStruct) MyMethod { 
    println("My statement."); 
} 

CallFunc("MyStruct", "MyMethod"); 
//print out "My statement." 

So I guess, first I need something like StructByName() and after that use it for MethodByName(), is that right!?

Upvotes: 41

Views: 56159

Answers (5)

Carson
Carson

Reputation: 7968

This is great. I added the return value example for it.

package main
import ("fmt";"math/rand";"reflect";"time")

// Invoke call the struct method and store the return values in a []reflect.Value
// return value example
// assume return value: (int, time.Time, error)
// var r []reflect.Value
// => r[0].interface() // for int
// => r[1].interface() // for time.Time
// => r[2].interface() // for error
// and so on.
func Invoke(obj any, name string, args ...any) []reflect.Value {
    inputs := make([]reflect.Value, len(args))
    for i, _ := range args {
        inputs[i] = reflect.ValueOf(args[i])
    }
    return reflect.ValueOf(obj).MethodByName(name).Call(inputs)
}

type Score struct{}

func (s Score) IsExcellent(score int) (bool, error) {
    if score < 0 {
        return false, fmt.Errorf("invalid score")
    }
    if score > 90 {
        return true, nil
    }
    return false, nil
}

func (s Score) Generate(min, max int) (int, time.Time) {
    rand.Seed(time.Now().UnixNano())
    return rand.Intn(max-min) + min, time.Now()
}

func main() {
    s := Score{}
    values1 := Invoke(s, "IsExcellent", 95)
    values2 := Invoke(s, "IsExcellent", -5)
    for _, values := range [][]reflect.Value{values1, values2} {
        if len(values) > 0 {
            err := values[1].Interface()
            if err != nil {
                fmt.Println(err.(error))
                continue
            }
            fmt.Println(values[0].Bool())
        }
    }
    values := Invoke(Score{}, "Generate", 0, 101)
    randNumber := values[0].Int()
    createTime := values[1].Interface().(time.Time) // It's better to check `values[1].Interface()` equal to nil first before you do assertion.
    fmt.Println(randNumber, createTime)
}

go playground

Upvotes: 1

Hao
Hao

Reputation: 455

package main

import (
    "fmt"
    "reflect"
)

type Log struct {
    Path  string
    Level string
}

func (l *Log) Conversion(i interface{}) {

    if data, ok := i.(*Log); ok {
        if data != nil {
            if len(data.Path) > 0 {
                l.Path = data.Path
            }
            if len(data.Level) > 0 {
                l.Level = data.Level
            }
        }
    }
}

type Storage struct {
    Type       string
    ServerList []string
}

func (s *Storage) Conversion(i interface{}) {

   if data, ok := i.(*Storage); ok {
        if data != nil {
            if len(data.Type) > 0 {
                s.Type = data.Type
            }
        }
    }
}

type Server struct {
    LogConfig     *Log
    StorageConfig *Storage
}

func main() {
    def := Server{
        LogConfig: &Log{
            Path:  "/your/old/log/path/",
            Level: "info",
        },
        StorageConfig: &Storage{
            Type:       "zookeeper",
            ServerList: []string{"127.0.0.1:2181"},
        },
    }

    fmt.Println(def)
    cur := Server{
        LogConfig: &Log{
            Path:  "/your/new/log/path/",
            Level: "debug",
        },
        StorageConfig: &Storage{
            Type:       "etcd",
            ServerList: []string{"127.0.0.1:2379"},
        },
    }

    fmt.Println(cur)

    defV := reflect.ValueOf(def)
    curV := reflect.ValueOf(cur)
    for k := 0; k < defV.NumField(); k++ {
        in := make([]reflect.Value, 1)
        in[0] = reflect.ValueOf(curV.Field(k).Interface())
        defV.Field(k).MethodByName("Conversion").Call(in)
    }

    fmt.Println(def.LogConfig)
    fmt.Println(def.StorageConfig)
}

Upvotes: 0

snyh
snyh

Reputation: 1295

type YourT1 struct {}
func (y YourT1) MethodBar() {
    //do something
}

type YourT2 struct {}
func (y YourT2) MethodFoo(i int, oo string) {
    //do something
}

func Invoke(any interface{}, name string, args... interface{}) {
    inputs := make([]reflect.Value, len(args))
    for i, _ := range args {
        inputs[i] = reflect.ValueOf(args[i])
    }
    reflect.ValueOf(any).MethodByName(name).Call(inputs)
}

func main() {
     Invoke(YourT2{}, "MethodFoo", 10, "abc")
     Invoke(YourT1{}, "MethodBar")
}

Really the code needs to check the method's input number and even whether the method itself exists.

You can reference this http://gowalker.org/reflect#Type

  1. Check "any" is a struct type
  2. Check "any" has "name" method
  3. Check the number of method "name" input parameters is equal the length of args
  4. Implement ret by reflect.Value.Interface()

and be careful the Ptr type;

Or you can use SomeInterface{} instead of directly using interface{} to ensure this "any" type, like this:

type Shape interface {
    Area() float64  //some method to ensure any is an Shape type.
}

func Invoke(s Shape, name string, inputs...interface{}) []interface{} {
}

so this is OK

color := Invoke(Circle{}, "GetColor")[0].(Color)

but

Invoke(NotAShape{}, "ForBar")

can't be compiled because NotAnShape isn't a Shape.

If you can't be sure about the first type at compile time, you can build a map to store all possible type, like this:

map[string]reflect.Value{
    "YourT1" : reflect.ValueOf(YourT1{})
    "YourT2" : reflect.ValueOf(YourT2{})
    "Circle" : reflect.ValueOf(Cirlce{}) // or reflect.ValueOf(&Circle{})
}  

Upvotes: 36

Ramil Gilfanov
Ramil Gilfanov

Reputation: 602

gist invoke struct method with error handling

// Invoke - firstResult, err := Invoke(AnyStructInterface, MethodName, Params...)
func invoke(any interface{}, name string, args ...interface{}) (reflect.Value, error) {
    method := reflect.ValueOf(any).MethodByName(name)
    methodType := method.Type()
    numIn := methodType.NumIn()
    if numIn > len(args) {
        return reflect.ValueOf(nil), fmt.Errorf("Method %s must have minimum %d params. Have %d", name, numIn, len(args))
    }
    if numIn != len(args) && !methodType.IsVariadic() {
        return reflect.ValueOf(nil), fmt.Errorf("Method %s must have %d params. Have %d", name, numIn, len(args))
    }
    in := make([]reflect.Value, len(args))
    for i := 0; i < len(args); i++ {
        var inType reflect.Type
        if methodType.IsVariadic() && i >= numIn-1 {
            inType = methodType.In(numIn - 1).Elem()
        } else {
            inType = methodType.In(i)
        }
        argValue := reflect.ValueOf(args[i])
        if !argValue.IsValid() {
            return reflect.ValueOf(nil), fmt.Errorf("Method %s. Param[%d] must be %s. Have %s", name, i, inType, argValue.String())
        }
        argType := argValue.Type()
        if argType.ConvertibleTo(inType) {
            in[i] = argValue.Convert(inType)
        } else {
            return reflect.ValueOf(nil), fmt.Errorf("Method %s. Param[%d] must be %s. Have %s", name, i, inType, argType)
        }
    }
    return method.Call(in)[0], nil
}

Upvotes: 3

user811773
user811773

Reputation:

To call a method on an object, first use reflect.ValueOf. Then find the method by name, and then finally call the found method. For example:

package main

import "fmt"
import "reflect"

type T struct {}

func (t *T) Foo() {
    fmt.Println("foo")
}

func main() {
    var t T
    reflect.ValueOf(&t).MethodByName("Foo").Call([]reflect.Value{})
}

Upvotes: 72

Related Questions