Calling a method with Go Reflect by name and with a parameter

自作多情 提交于 2019-12-25 01:45:47

问题


This is a follow on from Calling a function with Go Reflect .

To simplify the question I cut out what I could, hard coded some values and ~hopefully~ didn't make it unclear in the process. I'm getting an error on the code "method.Call(env)" near the bottom.

Ideally what I would like to do is minimize the use of reflection similarly how ThunderCat did on the previous question with the line:

method := miType.Method(i).Func.Interface().(func(core.ModuleInfo) core.ModuleInfo)

but if that's not possible the simplest way it can be done would be perfectly fine. If this seems like a basic question, my apologies, I am very new to Go.

The error I am getting is:

cannot use env (type Environment) as type []reflect.Value in argument to method.Call

which is because I would like to assert the method to the function with the correct signature as was done on the previous quesiton but after quite a bit of playing around I just haven't quite got it.

The simplified code:

package main

import (
  "flag"
  "fmt"
  "reflect"
)

type CommandLineFlags struct {
  Debug *bool
}

type Environment struct {
  CLF CommandLineFlags
}

type ModuleInfo struct {
  Initialize bool   // Flag: True of module has Initialization function and it should be called. Default: false
  Module     string // Name of the module. No need to hard code, will be set during initialization.
}

type ModuleInit struct{}

func main() {
  var env Environment

  env.CLF.Debug = flag.Bool("dbg", false, "Enables Debug Messages")
  flag.Parse()

  modules := make([]ModuleInfo, 1)
  modules[0].Initialize = true
  modules[0].Module = "logger"

  miValue := reflect.ValueOf(ModuleInit{})
  // miType := reflect.TypeOf(ModuleInit{})
  for _, m := range modules {
    if m.Initialize {
      funcName := m.Module + "Init"
      method := miValue.MethodByName(funcName)
      fmt.Println(funcName)
      // Would like to do something like this
      //    ...Func.Interface().(func(core.ModuleInit) core.ModuleInit)
      // like is done with the referenced quesiton above so as to minimize the use of reflect calls.
      method.Call(env)
    }
  }
}

func (mi ModuleInit) LoggerInit(env *Environment) {
  var debugEnabled = *env.CLF.Debug
  // ...and more stuff.
}

回答1:


The method has the type func(*Environment). Assert to that type and call:

modules := make([]ModuleInfo, 1)
modules[0].Initialize = true
modules[0].Module = "Logger"

miValue := reflect.ValueOf(ModuleInit{})
for _, m := range modules {
    if m.Initialize {
        funcName := m.Module + "Init"
        method := miValue.MethodByName(funcName).Interface().(func(*Environment))
        method(&env)
    }
}

Run it on the Playground.

(Note two issues fixed: The module should be "Logger", not "logger", method takes a *Environment, not an Environment.)

The code above will panic if the method is not found or does not have the correct type. Here's the code with checks to prevent a panic:

modules := make([]ModuleInfo, 1)
modules[0].Initialize = true
modules[0].Module = "Logger"

miValue := reflect.ValueOf(ModuleInit{})
for _, m := range modules {
    if m.Initialize {
        funcName := m.Module + "Init"
        method := miValue.MethodByName(funcName)
        if !method.IsValid() {
            fmt.Printf("method %s not found", funcName)
            continue
        }
        fn, ok := method.Interface().(func(*Environment))
        if !ok {
            fmt.Println("method is not func(*Environment)")
            continue
        }
        fn(&env)
    }
}

Run it on the Playground.




回答2:


There is several errors in OP code,

  • the func name was not properly generated,
  • the reflected method instance was not properly checked for validity,
  • env parameter of LoggerInit was a pointer, a value was sent in,
  • method call was not properly done.

Here is the fixed version (https://play.golang.org/p/FIEc6bTvGWJ).

package main

import (
    "flag"
    "fmt"
    "log"
    "reflect"
    "strings"
)

type CommandLineFlags struct {
    Debug *bool
}

type Environment struct {
    CLF CommandLineFlags
}

type ModuleInfo struct {
    Initialize bool   // Flag: True of module has Initialization function and it should be called. Default: false
    Module     string // Name of the module. No need to hard code, will be set during initialization.
}

type ModuleInit struct{}

func main() {
    var env Environment

    env.CLF.Debug = flag.Bool("dbg", false, "Enables Debug Messages")
    flag.Parse()

    modules := make([]ModuleInfo, 1)
    modules[0].Initialize = true
    modules[0].Module = "logger"

    miValue := reflect.ValueOf(ModuleInit{})
    // miType := reflect.TypeOf(ModuleInit{})
    for _, m := range modules {
        if m.Initialize {
            funcName := strings.Title(m.Module) + "Init"
            method := miValue.MethodByName(funcName)
            log.Printf("%#v %v\n", method, funcName)
            if !method.IsValid() || method.IsNil() {
                break
            }
            fmt.Println(funcName)
            // Would like to do something like this
            //    ...Func.Interface().(func(core.ModuleInit) core.ModuleInit)
            // like is done with the referenced quesiton above so as to minimize the use of reflect calls.
            out := method.Call([]reflect.Value{reflect.ValueOf(env)})
            fmt.Println(out) // A bunch of relfect.Values.
        }
    }
}

func (mi ModuleInit) LoggerInit(env Environment) {
    var debugEnabled = *env.CLF.Debug
    // ...and more stuff.
    log.Println("LoggerInit ", debugEnabled)
}



回答3:


The issue is that the argument passed reflect.Value.Call needs to be of the type reflect.Value itself. See the signature from https://golang.org/pkg/reflect/#Value.Call

func (v Value) Call(in []Value) []Value




回答4:


You must wrap the env variable in a []reflect.Value since reflect.Value.Call requires a slice of reflect.Value.

args := []reflect.Value{reflect.ValueOf(&env),}
method.Call(args)

Also, some typos in your code:

modules[0].Module = "Logger"


来源:https://stackoverflow.com/questions/53020517/calling-a-method-with-go-reflect-by-name-and-with-a-parameter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!