通过反射归零a * struct(不知道底层类型)并重新分配指向接口的指针

时间:2018-03-07 12:23:45

标签: go reflection

背景

我正在构建一个包装器包,它结合了已定义的命令,并允许它们在cli或交互式shell上下文中执行。命令在结构中定义,如下所示(显示相关字段):

type Handler func(c *Command) error

type Command struct {
    RequestHandler     Handler
    ResponseHandler    Handler
    Request            interface{} //pointer to struct
    Response           interface{} //pointer to struct
}

底层的Request / Response对象总是指向结构的指针,所以我非常大量地使用反射来递归迭代底层的结构字段,根据调用上下文(shell / cli)将它们的字段显示为标志或提示。然后,我根据用户输入使用反射填充请求对象。

到目前为止,非常好......

问题

当我在交互式shell上下文中时,可以调用一个命令,并填充请求结构及其值 - 一切都很好。但是,如果再次调用该命令,则仍会填充请求(并且响应可能也是如此),因此我希望实现“重置”功能。命令的方法,当从shell调用命令时可以在内部调用。

现在正如我所说,我可以保证请求和响应都是指向结构的指针。但是当我所拥有的只是一个接口并且没有基础类型的高级知识((和内部字段可能是结构值)时,如何将它们设置回各自初始化的nil值。

可能的方法

  1. 在命令注册时(在填充之前)添加底层请求/响应对象的第二个副本,然后在重置时将其复制

  2. 只需创建一个通用的重置结构函数,该函数作用于基础结构指针的任何接口

  3. 某些方法我不知道这对我来说是什么

  4. 我已经尝试了两种方式,而且我正在努力实施

    接近1:

    type Command struct {
        RequestHandler     Handler
        ResponseHandler    Handler
        zeroRequest        interface{}
        zeroResponse       interface{}
        Request            interface{} //pointer to struct
        Response           interface{} //pointer to struct
    }
    
    func (c *Command) register() error {
        // validate is pointer
        reqPtrVal := reflect.ValueOf(c.Request)
        if reqPtrVal.Kind() != reflect.Ptr {
            return ErrStructPtrExpected
        }
    
        // reflect the underlying value and validate is struct
        reqVal := reflect.Indirect(reqPtrVal)
        if reqVal.Kind() != reflect.Struct {
            return ErrStructPtrExpected
        }
    
        c.zeroRequest = reqVal.Interface()
    
        // other logic... 
    }
    
    func (c *Command) handleShell(sc *ishell.Context) {
        // reset request / response objects
    
        // reflect the underlying zero value and validate is struct
        reqVal := reflect.ValueOf(c.zeroRequest)
        if reqVal.Kind() != reflect.Struct {
            c.Commander.HandleError(ErrStructPtrExpected)
        }
    
        newRequest := reflect.New(reqVal.Type())
    
        // unfortunately below doesn't work as newRequest.CanAddr() == false
        c.zeroRequest = newRequest.Addr().Interface()
    
        // other logic
    }
    

    接近2: 这感觉有点干净(没有零值复制/更少的代码),但我甚至无法编译这种方法:

    func (c *Command) resetStruct(structPtr interface{}) error {
        // validate is pointer
        ptrVal := reflect.ValueOf(structPtr)
        if ptrVal.Kind() != reflect.Ptr {
            return ErrStructPtrExpected
        }
    
        // reflect the underlying value and validate is struct
        val := reflect.Indirect(ptrVal)
        if val.Kind() != reflect.Struct {
            return ErrStructPtrExpected
        }
    
        // create a new val from the underlying type and reassign the pointer
        newVal := reflect.New(val.Type())
        ptrVal.SetPointer(newVal.Addr())
        return nil
    }
    

    摘要

    最终目标是通过反射将这个未知类型的结构归零,并将指针重新分配回请求接口。接近的优先顺序是3,2,1 - 但任何有效的方法都只是花花公子。

    似乎我可以解决这个问题,如果我可以从reflect.Type创建一个具体的类型,但也许这里有另一种方法

1 个答案:

答案 0 :(得分:1)

您可以使用keyval = data.frame(GroupID = c('Group 1', 'Group 1', 'Group 1', 'Group 2', 'Group 2'), Major.sectors = c('Food, beverages, tobacco', 'Publishing, printing', 'Other services', 'Construction', 'Machinery, equipment, furniture, recycling'), stringsAsFactors = FALSE) left_join(df1, keyval) %>% head # Major.sectors EBIT.CAP GroupID #1 Food, beverages, tobacco -0.29998599 Group 1 #2 Machinery, equipment, furniture, recycling -10.11204781 Group 2 #3 Construction -0.05840266 Group 2 #4 Publishing, printing 1.56335275 Group 1 #5 Other services -1.87696308 Group 1 #6 Hotels & restaurants -0.93189920 <NA> reflect.Zero,请查看下面的代码段以获得清晰度

以下是如何使用它

reflect.New

播放:https://play.golang.com/p/8CJHHBL6c2p