切片和接口的类型转换

时间:2021-01-07 19:22:34

标签: go interface slice

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

这是一个 MCVE(从我的实际代码中非常简化)。 forEach 是一个遍历切片的函数,切片的类型在 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)
}

2 个答案:

答案 0 :(得分: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)
}

答案 1 :(得分:1)

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

// 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 不需要使用类型断言。

相关问题