2014-06-22 13 views
6

Mam funkcję, która iteruje przez wszystkie pola interfejsu przekazane jako parametr. Aby to osiągnąć, używam refleksji. Problem polega na tym, że nie wiem, jak uzyskać adres pola bez wskaźnika. Oto przykład:Uzyskaj wskaźnik do wartości za pomocą refleksji

type Z struct { 
    Id int 
} 

type V struct { 
    Id int 
    F Z 
} 

type T struct { 
    Id int 
    F V 
} 

Powyższy kod reprezentuje moje struktury testowe. Teraz tutaj jest rzeczywista funkcja, która przemierza określoną strukturę i wymienia dane o nim:

func InspectStruct(o interface{}) { 
    val := reflect.ValueOf(o) 
    if val.Kind() == reflect.Interface && !val.IsNil() { 
     elm := val.Elem() 
     if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr { 
      val = elm 
     } 
    } 
    if val.Kind() == reflect.Ptr { 
     val = val.Elem() 
    } 

    for i := 0; i < val.NumField(); i++ { 
     valueField := val.Field(i) 
     typeField := val.Type().Field(i) 
     address := "not-addressable" 

     if valueField.Kind() == reflect.Interface && !valueField.IsNil() { 
      elm := valueField.Elem() 
      if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr { 
       valueField = elm 
      } 
     } 
     if valueField.Kind() == reflect.Ptr { 
      valueField = valueField.Elem() 
     } 
     if valueField.CanAddr() { 
      address = fmt.Sprint(valueField.Addr().Pointer()) 
     } 

     fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name, 
      valueField.Interface(), address, typeField.Type, valueField.Kind()) 

     if valueField.Kind() == reflect.Struct { 
      InspectStruct(valueField.Interface()) 
     } 
    } 
} 

I tu jest rzeczywista próba po struktura konkretyzacji/inicjalizacji:

t := new(T) 
t.Id = 1 
t.F = *new(V) 
t.F.Id = 2 
t.F.F = *new(Z) 
t.F.F.Id = 3 

InspectStruct(t) 

I wreszcie wyjście InspectStruct rozmowy :

Field Name: Id, Field Value: 1,  Address: 408125440 , Field type: int , Field kind: int 
Field Name: F, Field Value: {2 {3}}, Address: 408125444 , Field type: main.V , Field kind: struct 
Field Name: Id, Field Value: 2,  Address: not-addressable , Field type: int , Field kind: int 
Field Name: F, Field Value: {3}, Address: not-addressable , Field type: main.Z , Field kind: struct 
Field Name: Id, Field Value: 3,  Address: not-addressable , Field type: int , Field kind: int 

Jak widać Używam rekursji, więc jeśli jedno z pól jest struct rodzaj potem zadzwonię InspectStruct dla niego. Mój problem polega na tym, że chociaż wszystkie pola zostały zainicjowane dla całej hierarchii "t" struktury, nie jestem w stanie uzyskać adresu dla dowolnego pola położonego na większej głębokości niż "t". Naprawdę doceniłbym każdą pomoc.

Odpowiedz

8

Przechodząc reflect.Value zamiast interface{} wydaje się rozwiązać ten problem, ale nie wiem dlaczego valueField.Interface() nie działa.

przykład robocza: http://play.golang.org/p/nleA2YWMj8

func InspectStructV(val reflect.Value) { 
    if val.Kind() == reflect.Interface && !val.IsNil() { 
     elm := val.Elem() 
     if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr { 
      val = elm 
     } 
    } 
    if val.Kind() == reflect.Ptr { 
     val = val.Elem() 
    } 

    for i := 0; i < val.NumField(); i++ { 
     valueField := val.Field(i) 
     typeField := val.Type().Field(i) 
     address := "not-addressable" 

     if valueField.Kind() == reflect.Interface && !valueField.IsNil() { 
      elm := valueField.Elem() 
      if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr { 
       valueField = elm 
      } 
     } 

     if valueField.Kind() == reflect.Ptr { 
      valueField = valueField.Elem() 

     } 
     if valueField.CanAddr() { 
      address = fmt.Sprintf("0x%X", valueField.Addr().Pointer()) 
     } 

     fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name, 
      valueField.Interface(), address, typeField.Type, valueField.Kind()) 

     if valueField.Kind() == reflect.Struct { 
      InspectStructV(valueField) 
     } 
    } 
} 

func InspectStruct(v interface{}) { 
    InspectStructV(reflect.ValueOf(v)) 
} 
10

miałem zamiar opuścić to jako komentarz do postu OneOfOne, ale okazało się zbyt skomplikowane dla komentarzu i bardzo istotne dla odpowiedzi na pytanie, dlaczego odpowiedź OneOfOne za działa poprawnie.

Przyczyna Interface() nie działa z powodu zwiniętego opakowania interfejsu. Aby dać wyobrażenie o tym, co się dzieje, spójrzmy na to, co robimy bez refleksji:

type MyStruct struct { 
    F Foo 
} 

type Foo struct { 
    i int 
} 

func ExtractField(ptr *MyStruct) interface{} { 
    return ptr.F 
} 

func main() { 
    ms := &MyStruct{Foo{5}} 
    f := ExtractField(ms).(Foo) // extract value 
    f.i = 19 
    fmt.Println(f, ms.F)   // ??? 
    fmt.Println(&f == &ms.F)  // Not the same! 
} 

(Playground)

jednak myśleć o interface{} to zwraca. Co to jest owijanie? Wartość z - to znaczy, że jest to jego. To właśnie robi value.Interface, zwraca ona interface{} zawijanie pola. Nie ma już metadanych wskaźnika, jest on całkowicie odłączony od oryginalnej struktury.

Jak można zauważyć, passing a value directly do reflect.ValueOf zawsze zwróci false dla CanAddr dla „top tier” - dlatego, że adres jest bez znaczenia, ponieważ to daje adres kopię wartości, zmieniając go wouldn Naprawdę nic nie znaczę. (Należy pamiętać, że wskaźniki również są wartościami - jeśli chcesz mieć adres pola o wartości wskaźnika, np. *Foo, naprawdę szukasz **Foo).

Tak więc w naszym przykładzie powyżej, gdybyśmy naiwnie przechodzą w reflect.ValueOf(ExtractField(ms)) byśmy uzyskać ValueOff, która nie tylko nie ma adresu chcesz - nie jest nawet adresowalnych według odzwierciedlać ponieważ nigdy nie poda prawidłowego adresu, jeśli chodzi o refleks (jedynym adresem, jaki może podać jest adres wewnętrznej wartości kopii w strukturze Value).

Dlaczego więc oddawanie króliczej dziury w dół działa? Cóż, jedynym prawdziwym sposobem na stwierdzenie, że reflect.Value zachowuje niezbędne metadane podczas korzystania z Elem i Field, podczas gdy interface{} nie może. Więc gdy reflect.Value może wyglądać następująco:

// Disclaimer: not the real structure of a reflect.Value 
type Value struct { 
    fieldAddress uintptr 
    value  Foo 
} 

Wszystko to może dać ci to za

// Again, an abstraction of the real interface wrapper 
// just for illustration purposes 
type interface{} struct { 
    value Foo 
} 
+0

dowiedziałem się od tego, dzięki za szczegółową odpowiedź. – OneOfOne

+1

Dzięki za uzupełnienie odpowiedzi @OneOfOne. Bardzo doceniane! –

1

Odpowiedź @OneofOne jest doskonały, ale lepiej jest dodać jedną dodatkową kontrolę

if valueField.IsValid() { 
     fmt.Printf("Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %v\n", typeField.Name, 
      valueField.Interface(), address, typeField.Type, valueField.Kind()) 
    } 

jest to potrzebne, ponieważ czasami można poprosić o interfejs z zerowej wartości struct. Dzieje się to, będzie panika.

1

Poszedłem dzisiaj do dziury od królika, wiele się nauczyłem, studiując ten kod i odpowiedź Jorsa, dziękuję. Doszedłem do innego wniosku, choć o swoim problemie, który doprowadził do być może bardziej proste rozwiązanie:

1) Jesteś przekazując wskaźnik do struktury, gdy pierwotnie wywołać funkcję, ale ...

2) Gdy powtarzasz się, wywołując "InspectStruct (valueField.Interface())", zamiast przekazywać strukturę osadzoną przez wskaźnik, przekazujesz ją według wartości.

Ponieważ przekazujesz według wartości, przejdź, utworzy tymczasowy i nie pozwoli ci wziąć adresu. Zamiast tego, kiedy powtarzasz, wywołaj valueField.Addr(). Interface(), który przekaże wskaźnik do osadzonej struktury.

if valueField.Kind() == reflect.Struct { 
-  InspectStruct(valueField.Interface()) 
+  InspectStruct(valueField.Addr().Interface()) 
    } 

Dzięki tej zmianie, mam wyjście spodziewasz:

Field Name: Id, Field Value: 1,  Address: 842350527552 , Field type: int , Field kind: int 
Field Name: F, Field Value: {2 {3}}, Address: 842350527560 , Field type: lib.V , Field kind: struct 
Field Name: Id, Field Value: 2,  Address: 842350527560 , Field type: int , Field kind: int 
Field Name: F, Field Value: {3}, Address: 842350527568 , Field type: lib.Z , Field kind: struct 
Field Name: Id, Field Value: 3,  Address: 842350527568 , Field type: int , Field kind: int 
Powiązane problemy