问题
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