alcance sobre a interface {} que armazena uma fatia

96

Dado o cenário em que você tem uma função que aceita t interface{}. Se for determinado que té uma fatia, como faço para rangesobre essa fatia?

func main() {
    data := []string{"one","two","three"}
    test(data)
    moredata := []int{1,2,3}
    test(data)
}

func test(t interface{}) {
    switch reflect.TypeOf(t).Kind() {
    case reflect.Slice:
        // how do I iterate here?
        for _,value := range t {
            fmt.Println(value)
        }
    }
}

Exemplo de Go Playground: http://play.golang.org/p/DNldAlNShB

Nucleon
fonte
Por que não fazer com que a função tenha uma [] interface {} em vez disso? Você está tentando lidar com vários tipos complexos?
Jeremy Wall
2
Sim, é para um sistema de modelos, então a interface {} pode ser um mapa, estrutura, fatia ou matriz. No código real, há muito mais instruções de caso, mas removi-as da postagem para tornar o problema mais conciso.
Nucleon
1
Jeremy, uma [] string não é um subtipo de [] interface {}, então você não pode chamar uma função func ([] interface {}) com uma [] string ou [] int, etc. Seria bom tem um recurso em Go para ter um tipo que significa "fatia de algo", onde você pode iterar sobre os elementos como interface {}, mas infelizmente você precisa de reflexão para isso.
Bjarke Ebert
@JeremyWall Você não pode usar a [] interface {} para atuar como fatia. Você pode consultar aqui .
firelyu de

Respostas:

137

Bem, eu usei reflect.ValueOfe então se for uma fatia você pode chamar Len()e Index()no valor para obter o lenda fatia e do elemento em um índice. Não acho que você será capaz de usar a operação de alcance para fazer isso.

package main

import "fmt"
import "reflect"

func main() {
    data := []string{"one","two","three"}
    test(data)
    moredata := []int{1,2,3}
    test(moredata)
} 

func test(t interface{}) {
    switch reflect.TypeOf(t).Kind() {
    case reflect.Slice:
        s := reflect.ValueOf(t)

        for i := 0; i < s.Len(); i++ {
            fmt.Println(s.Index(i))
        }
    }
}

Exemplo de Go Playground: http://play.golang.org/p/gQhCTiwPAq

masebase
fonte
23
Funciona muito bem, obrigado. A única coisa a adicionar seria que s.Index(i)retorna um, reflect.Valueportanto, no meu caso, eu precisava s.Index(i).Interface()fazer referência ao valor real.
Nucleon
1
O que você faria se tivesse um ponteiro para uma fatia interface{}? por exemplo moredata := &[]int{1,2,3}
Ryan Walls
1
Respondendo à minha pergunta: se você tiver um ponteiro para uma fatia em vez de uma fatia, precisará usar Elem()para obter o valor subjacente. por exemplo reflect.TypeOf(reflect.ValueOf(t).Elem().Interface()).Kind()
Ryan Walls
e se eu quiser obter o valor do campo de interface da fatia da interface sem qualquer estrutura. Eu tentei essa solução, mas ela é limitada para obter referência de interface apenas da fatia e não dos valores reais dos campos.
Amandeep Kaur
woh muito obrigado, esse truque me economizou muito tempo (e dor de cabeça!)
Nicolas Garnier
26

Você não precisa usar reflexão se souber quais tipos esperar. Você pode usar uma chave de tipo , como esta:

package main

import "fmt"

func main() {
    loop([]string{"one", "two", "three"})
    loop([]int{1, 2, 3})
}

func loop(t interface{}) {
    switch t := t.(type) {
    case []string:
        for _, value := range t {
            fmt.Println(value)
        }
    case []int:
        for _, value := range t {
            fmt.Println(value)
        }
    }
}

Verifique o código no playground .

Inanc Gumus
fonte
Porém, isso não funcionará em uma matriz, apenas em uma fatia
user1028741
@ user1028741 Não, o código funciona para todos os tipos, incluindo arrays + Você sempre pode pegar uma fatia de um array array[:].
Inanc Gumus
Mas o tipo de array será com a capacidade real dele. [3] int não é do mesmo tipo que [2] int ou [] int. Portanto, se o tipo de 't' for realmente uma matriz, o caso relevante não se aplicará.
user1028741
O que quero dizer é que seu exemplo não funcionará em matrizes. Eu mudei aqui: play.golang.org/p/DAsg_0aXz-r
user1028741
Você não pode fazer isso funcionar facilmente, se você não souber o tipo de seu parâmetro em primeiro lugar. Para usar seu truque (array [:]), você terá que usar a reflexão para decidir que é um array, então lançar em array - e então gerar uma fatia dele. É por isso que eu disse que funciona em uma fatia, não em uma matriz. Obviamente, com esforço suficiente, você pode produzir uma fatia da matriz ...
user1028741,
4

há uma exceção na maneira como a interface {} se comporta, @Jeremy Wall já deu um ponteiro. se os dados transmitidos forem definidos como [] interface {} inicialmente.

package main

import (
    "fmt"
)

type interfaceSliceType []interface{}

var interfaceAsSlice interfaceSliceType

func main() {
    loop(append(interfaceAsSlice, 1, 2, 3))
    loop(append(interfaceAsSlice, "1", "2", "3"))
    // or
    loop([]interface{}{[]string{"1"}, []string{"2"}, []string{"3"}})
    fmt.Println("------------------")


    // and of course one such slice can hold any type
    loop(interfaceSliceType{"string", 999, map[int]string{3: "three"}})
}

func loop(slice []interface{}) {
    for _, elem := range slice {
        switch elemTyped := elem.(type) {
        case int:
            fmt.Println("int:", elemTyped)
        case string:
            fmt.Println("string:", elemTyped)
        case []string:
            fmt.Println("[]string:", elemTyped)
        case interface{}:
            fmt.Println("map:", elemTyped)
        }
    }
}

resultado:

int: 1
int: 2
int: 3
string: 1
string: 2
string: 3
[]string: [1]
[]string: [2]
[]string: [3]
------------------
string: string
int: 999
map: map[3:three]

Experimente

Matus Kral
fonte