web-dev-qa-db-ja.com

Golangでエクスポートされていない構造体フィールドにアクセスする方法は?

リフレクトを使用してgo 1.8でエクスポートされていないフィールドにアクセスする方法はありますか?これはもはや機能していないようです: https://stackoverflow.com/a/17982725/55549

ご了承ください reflect.DeepEqualは問題なく動作します(つまり、エクスポートされていないフィールドにアクセスできます)が、その関数の先頭または末尾を作成することはできません。実際の動作を示すgo playareaは次のとおりです https://play.golang.org/p/vyEvay6eVG 。 srcコードは以下のとおりです

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
}
11
U Avalos

構造体がアドレス可能な場合、unsafe.Pointerを使用して、次のようにフィールドにアクセスできます(読み取りまたは書き込み)。

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.

遊び場で完全な例を参照してください。

unsafe.Pointerのこの使用法は、unsafeパッケージのドキュメントおよびgo vetsのドキュメントによると「有効」です。

構造体がアドレス可能でない場合、このトリックは機能しませんが、次のようにアドレス可能なコピーを作成できます。

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.

遊び場で完全な例を参照してください。

13
cpcallen

reflect.DeepEqual() は、それが reflect パッケージのエクスポートされていない機能にアクセスできるため、これを行うことができます。この場合、つまりvalueInterface()関数は、safe引数を受け取り、エクスポートされていないものへのアクセスを拒否します Value.Interface() メソッドのフィールド値(safe=trueの場合)。 reflect.DeepEqual()は、safe=falseを渡すことを呼び出す可能性があります。

引き続き実行できますが、エクスポートされていないフィールドにValue.Interface()を使用することはできません。代わりに、型固有のメソッドを使用する必要があります。たとえば、stringには Value.String() 、浮動小数点数には Value.Float() 、intには Value.Int() などです。これらは値のコピー(これで検査するのに十分です)ですが、フィールドの値を変更することはできません(Value.Interface()が機能し、フィールドタイプがポインタタイプである場合は、「部分的に」可能である可能性があります)。

フィールドがたまたまインターフェース型である場合は、 Value.Elem() を使用して、インターフェース値に含まれている値/ラップされている値を取得できます。

実証するには:

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())
}

出力( Go Playground で試してください):

string hello
int64 2
float64 3
2
icza

cpcallen の作業に基づく:

_import (
    "reflect"
    "unsafe"
)

func GetUnexportedField(field reflect.Value) interface{} {
    return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface()
}

func SetUnexportedField(field reflect.Value, value interface{}) {
    reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).
        Elem().
        Set(reflect.ValueOf(value))
}

_

_reflect.NewAt_は最初は読みにくいかもしれません。 field.Type()をそのポインターとして使用して、指定されたunsafe.Pointer(field.UnsafeAddr())の値へのポインターを表す_reflect.Value_を返します。このコンテキストでは、_reflect.NewAt_は_reflect.New_とは異なり、新しく初期化された値へのポインタを返します。

例:

_type Foo struct {
    unexportedField string
}

GetUnexportedField(reflect.ValueOf(&Foo{}).Elem().FieldByName("unexportedField"))
_

https://play.golang.org/p/IgjlQPYdKFR

0
mattes