【问题标题】:Calling a method with Go Reflect by name and with a parameter通过名称和参数调用使用 Go Reflect 的方法
【发布时间】:2019-03-31 21:50:45
【问题描述】:

这是Calling a function with Go Reflect 的后续。

为了简化问题,我尽可能地删减了一些值,对一些值进行了硬编码,~希望~在这个过程中没有让它不清楚。我在底部附近的代码“method.Call(env)”上遇到错误。

理想情况下,我想做的是尽量减少反射的使用,就像 ThunderCat 在上一个问题中所做的那样:

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

但如果这不可能,那么最简单的方法就可以了。如果这似乎是一个基本问题,我很抱歉,我是 Go 新手。

我得到的错误是:

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

这是因为我想用正确的签名将方法声明给函数,就像在上一个问题中所做的那样,但是在玩了很多之后我还没有完全明白。

简化代码:

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.
}

【问题讨论】:

    标签: go reflect


    【解决方案1】:

    问题是传递给reflect.Value.Call 的参数本身必须是reflect.Value 类型。查看https://golang.org/pkg/reflect/#Value.Call的签名

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

    【讨论】:

      【解决方案2】:

      您必须将env 变量包装在[]reflect.Value 中,因为reflect.Value.Call 需要reflect.Value 的切片。

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

      另外,您的代码中有一些拼写错误:

      modules[0].Module = "Logger"
      

      【讨论】:

      • 是的,在原始代码中不是这样,这是我的错误,因为我在这里对其进行了修剪。这些值是通过代码收集的,所以真的不会错。
      【解决方案3】:

      OP代码有几个错误,

      • 函数名称未正确生成,
      • 反射的方法实例没有正确检查有效性,
      • Lo​​ggerInit的env参数是一个指针,传入了一个值,
      • 方法调用未正确完成。

      这是固定版本 (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)
      }
      

      【讨论】:

        【解决方案4】:

        该方法的类型为func(*Environment)Assert to that type 并致电:

        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.

        (注意修复了两个问题:模块应该是"Logger",而不是"logger",方法采用*Environment,而不是Environment。)

        如果方法没有找到或者没有正确的类型,上面的代码将会崩溃。这是带有检查以防止恐慌的代码:

        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.

        【讨论】:

        • 如果我知道我迭代的每个方法都具有完全相同的签名,因此我认为是有效的,我认为我不需要“IsValid()”条件。或者,以问题的形式提出,除了我编程的类型(例如内部方法)之外,是否还有其他类型的方法?
        • 唯一的方法是那些由应用程序定义的。问题中的代码与两种情况都存在冲突:有效检查处理不正确的方法名称,类型断言中的 ok 处理不正确的函数签名。我会保留此检查并返回错误或恐慌并带有信息性错误消息。
        猜你喜欢
        • 2019-03-30
        • 2013-02-03
        • 1970-01-01
        • 1970-01-01
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-12
        相关资源
        最近更新 更多