2017-03-08 3 views
3

Existe-t-il un moyen d'utiliser Reflect pour accéder aux champs non exportés dans go 1.8? Cela ne semble fonctionner: https://stackoverflow.com/a/17982725/555493Accéder aux champs non exportés dans golang/refléter?

Notez que reflect.DeepEqual fonctionne très bien (qui est, il peut accéder aux champs non exportées) mais je ne peux ni queue ni tête de cette fonction. Voici une zone de jeu qui le montre en action: https://play.golang.org/p/vyEvay6eVG. Le code src est inférieure

import (
"fmt" 
"reflect" 
) 

type Foo struct { 
    private string 
} 

func main() { 
    x := Foo{"hello"} 
    y := Foo{"goodbye"} 
    z := Foo{"hello"} 

    fmt.Println(reflect.DeepEqual(x,y)) //false 
    fmt.Println(reflect.DeepEqual(x,z)) //true 
} 

Répondre

1

reflect.DeepEqual() peut le faire parce qu'il a accès à des fonctions non exportées du paquet reflect, dans ce cas, à savoir pour la fonction valueInterface(), qui prend un argument safe, qui refuse l'accès aux valeurs de champ non exportées via la méthode Value.Interface() si safe=true. reflect.DeepEqual() va (peut-être) appeler cela passant safe=false.

Vous pouvez toujours le faire, mais vous ne pouvez pas utiliser Value.Interface() pour les champs non exportés. Au lieu de cela, vous devez utiliser des méthodes spécifiques au type, telles que Value.String() pour string, Value.Float() pour les flottants, Value.Int() pour les ints, etc. Cela vous renverra une copie de la valeur (ce qui est suffisant pour l'inspecter), mais ne vous permettra pas de modifier la valeur du champ (qui pourrait être "partiellement" possible si Value.Interface() fonctionnerait et le type de champ serait un type de pointeur).

Si un champ se trouve être un type d'interface, vous pouvez utiliser Value.Elem() pour obtenir la valeur contenue/encapsulée par la valeur de l'interface.

Pour démontrer:

type Foo struct { 
    s string 
    i int 
    j interface{} 
} 

func main() { 
    x := Foo{"hello", 2, 3.0} 
    v := reflect.ValueOf(x) 

    s := v.FieldByName("s") 
    fmt.Printf("%T %v\n", s.String(), s.String()) 

    i := v.FieldByName("i") 
    fmt.Printf("%T %v\n", i.Int(), i.Int()) 

    j := v.FieldByName("j").Elem() 
    fmt.Printf("%T %v\n", j.Float(), j.Float()) 
} 

sortie (essayez le Go Playground):

string hello 
int64 2 
float64 3 
+0

Ok, mais comment pouvez-vous savoir ce que le type est ainsi que vous pouvez appeler la bonne méthode? –

+0

@UAvalos E.g. en appelant ['Value.Type()'] (https://golang.org/pkg/reflect/#Value.Type) (sur la valeur du champ). – icza

+0

ne se traduirait-il pas par un bloc de commutation vraiment douloureux? ex: cas Uint, cas Uint8, Uint16, etc ... –

6

Si la struct est adressable, vous pouvez utiliser unsafe.Pointer pour accéder au champ (lire ou écrire), comme ceci:

rs := reflect.ValueOf(&MyStruct).Elem() 
rf := rs.Field(n) 
// rf can't be read or set. 
rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() 
// Now rf can be read and set. 

See full example on the playground.

Cette utilisation de unsafe.Pointer est « valide » selon la la documentation du paquet unsafe et go vet s proprement.

Si la struct est adressable pas cette astuce ne fonctionnera pas, mais vous pouvez créer une copie adressable comme ceci:

rs = reflect.ValueOf(MyStruct) 
rs2 := reflect.New(rs.Type()).Elem() 
rs2.Set(rs) 
rf = rs2.Field(0) 
rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem() 
// Now rf can be read. Setting will succeed but only affects the temporary copy. 

See full example on the playground.

+0

Tour brillant, merci. Combiné avec 'FieldByName', c'est assez puissant, mais sale. – user1112789