【问题标题】:Type conversions of slices and interfaces切片和接口的类型转换
【发布时间】:2021-04-13 13:14:15
【问题描述】:

如果我有一个函数传递了一个interface{},我可以确定(通过其他方式)是一个切片,但不能确定它是一个切片,我如何迭代它(最好不使用反射)?

这是一个 MCVE(从我的实际代码非常简化)。 forEach 是一个遍历 a 切片的函数,其类型在 main() 的调用堆栈中“丢失”。它试图说“啊哈,一个切片,我将遍历它一个interface{} 的切片并在每个值上调用传入的onEach 函数”。这失败了,因为“转换”类型因此失败:

panic: interface conversion: interface {} is []string, not []interface {}

我很清楚为什么类型“转换”会失败,即它不是真正的类型转换,而是一个断言。但是考虑到(如果我可以迭代)我可以断言每个切片成员,这原则上应该是可行的。

假设我实际上想要一个像 forEach 这样的迭代器,它可以做到这一点(而不是 forEachStringforEachInt 等)。有没有好的方法来做到这一点?最好没有反射(虽然我想这没关系),但肯定没有反射涉及每种类型的案例(这就是首先拥有forEach 函数的意义)?

我知道(尚未实施的)泛型提案对此非常有效,但我希望使用现有技术来做到这一点!

package main

import (
    "fmt"
)

type onEach func(x interface{})

func printString(x interface{}) {
    xx := x.(string)
    fmt.Printf("x is a string '%s'\n", xx)
}

func printInt(x interface{}) {
    xx := x.(int)
    fmt.Printf("x is an int '%d'\n", xx)
}

func forEach(y interface{}, onEach onEach) {
    // code to ensure y is a slice omitted
    a := y.([]interface{}) // <-------- THIS LINE PANICS
    for _, x := range a {
        onEach(x)
    }
}

func main() {
    s := []string{"foo", "bar"}
    i := []int{1, 2, 3}
    forEach(s, printString)
    forEach(i, printInt)
}

【问题讨论】:

  • 您可以枚举所有可能的切片类型,也可以使用反射。没有别的办法。
  • @JimB 即使用Value.Len()Value.Index() 等?我想这就是我必须做的 - 叹息。
  • @JimB OK - 用示例代码添加了一个答案,以防其他人需要这个。

标签: go interface slice


【解决方案1】:

所以这是一个使用反射的答案,我想这不会太难看。

package main

import (
    "fmt"
    "reflect"
)

type onEach func(x interface{})

func printString(x interface{}) {
    xx := x.(string)
    fmt.Printf("x is a string '%s'\n", xx)
}

func printInt(x interface{}) {
    xx := x.(int)
    fmt.Printf("x is an int '%d'\n", xx)
}

func forEach(y interface{}, onEach onEach) {
    // code to ensure y is a slice omitted
    v := reflect.ValueOf(y)
    for i := 0; i < v.Len(); i++ {
        onEach(v.Index(i).Interface())
    }
}

func main() {
    s := []string{"foo", "bar"}
    i := []int{1, 2, 3}
    forEach(s, printString)
    forEach(i, printInt)
}

【讨论】:

    【解决方案2】:

    使用反射包在任意类型的切片上编写迭代函数:

    // forEach calls f for each element of slice s.
    // The function f must have a single argument with
    // the same type as the slice's element type.
    func forEach(s interface{}, f interface{}) {
        sv := reflect.ValueOf(s)
        fv := reflect.ValueOf(f)
        for i := 0; i < sv.Len(); i++ {
            fv.Call([]reflect.Value{sv.Index(i)})
        }
    }
    

    像这样使用它:

    func printString(s string) {
        fmt.Printf("x is a string %q\n", s)
    }
    
    s := []string{"foo", "bar"}
    forEach(s, printString)
    

    这个答案与问题中的代码和另一个答案不同,因为函数f 不需要使用类型断言。

    【讨论】:

      猜你喜欢
      • 2012-09-27
      • 2018-11-28
      • 1970-01-01
      • 2020-04-17
      • 1970-01-01
      • 2012-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多