41

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!?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
nvcnvn
  • 4,991
  • 8
  • 49
  • 77
  • since MyMethod is a method of *MyStruct, I believe you would at least need an instance of *MyStruct to cal MyMethod with. Maybe it's assumed that CallFunc creates a zeroed instaces of MyStruct? – gregghz Nov 12 '11 at 20:52
  • the hard thing is I don't know the type of the Struct yet! – nvcnvn Nov 13 '11 at 02:44
  • Here if you are expecting the ` CallFunc("MyStruct", "MyMethod"); ` to instanciate the struct and execute the function which need to be an impure function to make sense then only reason you have the * there is to not have a pass by value of huge struct.So in essence the struct would work just as a namespace – Sarath Sadasivan Pillai Jul 01 '16 at 12:34
  • I have posted a number of [helpful examples on using reflection](https://github.com/Xeoncross/go-reflection-examples/). – Xeoncross Sep 24 '19 at 22:45

5 Answers5

71

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{})
}
  • The issue in my case Is I cant not declare *t* is typed *T*, its must be some how I can declare *t* typed *T* by the name of *T* is string "T". – nvcnvn Nov 13 '11 at 02:47
  • @nvcnvn: I would suggest to match the name against the string "T" somewhere in your code and create a value of type T if the name matched. If it matches some other type "U", create a value of type U. The values can be freely passed around as interface{}. –  Nov 13 '11 at 09:40
  • Thank you @Atom, what if the method Foo has any arguments? And has any return values? –  Feb 04 '14 at 16:44
  • 1
    ``` func callMethod(methodName string, reflectValue reflect.Value) { // Only get address from non-pointer if reflectValue.CanAddr() && reflectValue.Kind() != reflect.Ptr { reflectValue = reflectValue.Addr() } if methodValue := reflectValue.MethodByName(methodName);methodValue.IsValid() { switch method := methodValue.Interface().(type) { case func(): method() case func() error: log.LogError(method()) .............. } } ``` – Sarath Sadasivan Pillai Jul 01 '16 at 12:27
  • this just outputs `[]` rather than the value returned by the function for me – Bassie Oct 24 '21 at 01:33
36
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{})
}  
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
snyh
  • 1,225
  • 14
  • 19
  • thank you so much for the code. How about the method has any return values? –  Feb 04 '14 at 16:55
  • 1
    the return values is more bother becaus you have to manually convert the reflect.Value to your excepted value type. http://gowalker.org/reflect#Value_Call will return an array of reflect.Values as return values. reflect.Value has the method *Interface* which will return the an interface. – snyh Feb 07 '14 at 06:50
  • snyh, can you explain it more? I'll be very grateful if you explain how can i convert `[]reflect.Value` here to `error`, because my function has this signature `func doSmth() error {...}` – vodolaz095 May 17 '15 at 21:41
  • 1
    @vodolaz095 https://gowalker.org/reflect#Value_Interface err := fmt.Errorf("a error example") vv := reflect.ValueOf(err) s, ok := vv.Interface().(error) if ok { fmt.Println(s) } ` – snyh May 19 '15 at 05:36
3

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
}
Ramil Gilfanov
  • 552
  • 1
  • 8
  • 12
0
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)
}
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Hao
  • 455
  • 4
  • 8
0

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

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

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

Carson
  • 6,105
  • 2
  • 37
  • 45