Quantcast
Channel: プログラミング
Viewing all articles
Browse latest Browse all 8562

Goの勉強 リフレクション - あしたからがんばる

$
0
0

Goの勉強をやり始めたのでメモ。
プログラミング言語 Go」を読んでる。

  • 不明な型の変数に対して、その変数の情報を調べたり操作したりする機能をリフレクションと呼ぶ。

なぜリフレクションなのか

  • 不明な型の変数を取り扱いたケースはある。
    • 例えばfmt.printf()の第2引数以降には多くの型を渡せる。
  • 型switchを使えば一見できそうだが、あまり現実的ではない。
    • すべての型のケースを書く?
    • すべてのパッケージに依存してしまう。
    • 基底型と派生型は一致しない。
  • ここでリフレクションが必要になる。

reflect.Type と reflect.Value

  • reflectパッケージにはreflect.Typereflect.Valueという型がある。
  • reflect.Typeはその変数の動的な型を表す。
    • reflect.TypeOf(i interface{})を使用して取得する。
      • interface{}への暗黙的な変換をすると、変数は動的な型と動的な値を持つ。
      • 動的な型は基本的に具象型を指す。
      • よって、io.Writerを渡してもreflect.TypeOf()で取得できるのは*os.Fileなどである。
    • printfでは %Tを使用するとその変数の動的な型を出力する。
  • reflect.Valueはその変数の動的な値を表す。
    • reflect.ValueOf(i interface{})を使用して取得する。
    • reflect.Value.String()はその値を返さないので注意すること。
      • 値がほしい場合は printf%vを使用する。
  • reflect.Value.Interface()を使用することで interface{}に変換できる。
  • reflect.Typeで表現される型は無限にあるが、reflect.Kind(i interface{})で取得できる型の種類は有限である。

reflect.Valueでの変数の設定

  • Goの式の中には変数を示すものもあれば、変数を表さないものもある。
    • x, x.f[1], *pは変数を示す。
    • x+1, f(2)は変数を示さない。
  • 変数は値を含むアドレス化可能なメモリ位置であり、その値はアドレスを通じて変更できる。
  • reflect.Valueも同様にアドレス化可能なものやそうではないものがある。
    • reflect.Value(x)reflect.Value(&x)はアドレス化可能ではない。
    • reflect.Value(&x).Elem()はアドレス化可能である。
  • どのような変数xにでも、reflect.Value(&x).Elem()を呼び出すことでアドレス化可能な Valueを得られる。
  • ポインタを通してアクセスした場合にはアドレス化可能な Valueを得られる。
    • あるアドレス化可能ではないスライス e に対して、eはアドレス化可能ではないが、e[i]はアドレス化可能である。
    • あるアドレス化可能ではないスライス e に対して、reflect.Value(e)はアドレス化可能ではないが、 reflect.Value(e).Index(i)はアドレス化可能である。
  • 以下のようにすることでリフレクションを通じて変数の値を書き換えることができる。
x := 2
d := reflect.ValueOf(&x).Elem() // アドレス化可能な式  
px := d.Addr().Interface().(*int) // アドレス化可能な式のアドレス(ポインタ)  
*px = 3
fmt.Println(x) // 3  
  • これを reflect.Value.Set()を使用すると簡単に書ける。
d.Set(reflect.ValueOf(3))
  • アドレス化可能ではない reflect.Valueや、設定しようとする値の型が違う場合はパニックになる。
  • SetInt()などの基本型に特化したSetもある。
    • 基底型が等しければ成功する。
    • 値が大きすぎる場合は切り詰められる。
    • interface{}型への設定は失敗する。
var x interface{}
rx := reflect.ValueOf(&x).Elem()
rx.SetInt(2) // panic  
rx.Set(reflect.ValueOf(3)) // Setの場合はOK  
  • パッケージ非公開のフィールドは取得できるが更新はできない。
  • Set()が使用可能かを調べるには、アドレス化可能であるかを調べる CanAddr()ではなく CanSet()を使うべきである。

構造体のフィールドタグへのアクセス

以下のようなコードでタグにアクセスできる。

// ptr はタグの付いた構造体  
v := reflect.ValueOf(ptr).Elem()for i := 0; i < v.NumField(); i++ {
    fieldInfo := v.Type().Field(i)
    tag := fieldInfo.Tag
    name := tag.Get("http")
}

型のメソッドを表示する

  • relect.Typereflect.ValueMethodというメソッドを持っている。
  • t.Method(i)reflect.Methodを返し、 v.Method(i)reflect.Valueを返す。
  • reflect.Value.Callによって呼び出すことができる。

注意書き

  • リフレクションは注意して使うべきである。
    • リフレクションに基づくコードは脆弱になりえる。
      • 基本的にコンパイラでミスを発見できず、実行時にpanicになる。
    • ドキュメンテーションとしての型がないため、可読性が下がる
    • 実行速度が遅い

Viewing all articles
Browse latest Browse all 8562

Trending Articles