【问题标题】:How to range over slice of a custom type如何在自定义类型的切片上进行范围
【发布时间】:2019-12-13 13:46:23
【问题描述】:

我正在尝试为 Google DataStore 编写 Go 自定义缓存(更准确地说 - 围绕现有缓存库之一的包装器)。在缓存初始化时,它应该接受任何自定义类型的结构(带有适当定义的数据存储字段),这将成为所有存储项目的基础。这个想法是可以为各种类型创建/初始化缓存,这些类型反映了特定 DataStore 条目的结构 (CustomEntry)

方法 1 - 存储 reflect.Type 并使用它。 遇到的问题 - 无法遍历自定义类型的切片

type CustomEntry struct {
    Data struct {
        name          string   `datastore:"name,noindex"`
        address       []string `datastore:"address,noindex"`
    } `datastore:"data,noindex"`
}

func (cache *MyCache) CacheData(dataQuery string, dataType reflect.Type) {
    slice := reflect.MakeSlice(reflect.SliceOf(dataType), 10, 10)

    if keys, err := DataStoreClient.GetAll(cache.ctx, datastore.NewQuery(dataQuery), &slice); err != nil {
        //handle error
    } else {
        for i, dataEntry:= range slice {
        // ERROR: Cannot range over 'slice' (type Value)
            cache.Set(keys[i].Name, dataEntry)
        }
}

//usage: Cache.CacheData("Person", reflect.TypeOf(CustomEntry{})

方法 2 - 接受接口数组作为参数。 遇到的问题 = []CustomEntry 不是[]interface{}

func (cache *MyCache) CacheData(dataQuery string, dataType []interface{}) {
    if keys, err := DataStoreClient.GetAll(cache.ctx, datastore.NewQuery(dataQuery), &dataType); err != nil {
        //handle error
    } else {
        for i, dataEntry:= range slice {
        // this seems to work fine
            cache.Set(keys[i].Name, dataEntry)
        }
}

//usage: 
var dataType []CustomEntry
Cache.CacheData("Person", data)
// ERROR: Cannot use 'data' (type []CustomEntry) as type []interface{}

任何建议将不胜感激。

【问题讨论】:

  • 你查过stackoverflow.com/a/34163477/4466350这样的Q吗?
  • 是的,我进行了广泛而深入的搜索。通常的解决方案是在迭代之前简单地进行类型断言,但是 - 据我了解 - 这不能通过反射来完成(当您事先不知道您实际断言的类型时)。这就是为什么我认为提供更多关于我的用例的上下文可能会引导一些更有经验的人找到一些潜在的解决方法。
  • Go 中没有动态类型。
  • @Zack 缓存或无缓存,如果您希望它们被声明它们的包之外的包访问,请不要使用未导出的字段。 reflect.MakeSlice 返回一个 reflect.Value 类型的值,因此将 &slice 传递给 GetAll错误的,并且试图通过 slice 传递 range 也是错误的。查看reflect 包的文档以了解如何从slice 获取*[]<CustomType>(这是您需要传递给GetAll)以及如何迭代any 的元素reflect.Value 包含的切片。

标签: go


【解决方案1】:

我找到了一个解决方案,并认为值得分享,以防其他人遇到类似问题。

最简单的方法是初始化 DataStore 预期接收的结构切片,然后将指向它的指针作为参数 (interface{}) 传递给所需的函数。 DataStore,类似于一些解组函数(我尝试过使用 JSON 包)将能够成功地将数据附加到它。

尝试在函数内动态创建切片,给定特定类型,然后由函数(例如 DataStore 客户端)接受,这可能非常困难(我还没有设法找到方法)。同样,传递一部分接口(以便于迭代)只会使事情复杂化。

其次,为了迭代数据(例如,将其存储在缓存中),有必要: (1) 检索接口的底层值(即指针本身)——这可以使用reflect.ValueOf(pointerInterface) 来实现, (2) 取消引用指针,以便我们可以访问底层的、可迭代的结构切片——这可以通过调用.Elem() 来完成, (3) 使用.Index(i) 方法迭代底层切片(range 不会接受接口,即使底层类型是可迭代的)。

当然,添加一些 switch-case 语句可能适合确保捕获任何错误而不是导致运行时恐慌。

因此,以下代码为上述问题提供了一个可行的解决方案:

主要:

var data []customEntry
c.CacheData("Person",&data)

还有函数本身:

func (cache *MyCache) CacheData(dataQuery string, data interface{}) error {
    if keys, err := DataStoreClient.GetAll(cache.ctx, datastore.NewQuery(dataQuery), data); err != nil {
        return err
    } else {
        s := reflect.ValueOf(data).Elem()
        for i := 0; i < s.Len(); i++ {
            cache.Set(keys[i].Name, s.Index(i), 1)
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 2021-02-18
    • 1970-01-01
    • 2012-09-26
    • 1970-01-01
    • 2015-05-15
    • 2017-07-13
    相关资源
    最近更新 更多