LoginSignup
12
8

More than 5 years have passed since last update.

Goのややこしいリフレクションのスライス生成を解説

Last updated at Posted at 2017-07-09

オブジェクトのタイプを渡すと、そのスライスをプログラムで作る汎用処理的なコードを書きたい時がある。結構ややこしくてハマったので、同じようにハマってる人のために書き記しておきます。

実行環境はGo 1.7です。

サンプルコード

package main

import (
    "reflect"
    "fmt"
)

type Sns struct {
    Name string
}

func main() {
    var object Sns
    var objectPointer *Sns
    var slice []Sns
    var objectType reflect.Type
    var objectValue reflect.Value
    var sliceType reflect.Type
    var slicePointer *[]Sns
    var sliceValue reflect.Value
    var sliceInterface interface{}
    var slicePointerInterface interface{}

    object = Sns{}
    objectType = reflect.TypeOf(object)
    objectValue = reflect.New(objectType)
    objectPointer = objectValue.Interface().(*Sns)
    objectPointer.Name = "Twitter"
    fmt.Println(objectPointer.Name) // -> Twitter
    //
    // Newで生成
    //
    sliceType = reflect.TypeOf([]Sns{})
    sliceValue = reflect.New(sliceType)
    slicePointerInterface = sliceValue.Interface()
    slicePointer = slicePointerInterface.(*[]Sns)
    slice = append(*slicePointer, Sns{ Name: "Facebook"})
    fmt.Println(slice[0].Name) // -> Facebook
    //
    // MakeSliceで生成
    //
    sliceValue = reflect.MakeSlice(sliceType, 0, 0)
    sliceInterface = sliceValue.Interface()
    slice = sliceInterface.([]Sns)
    slice = append(slice, Sns{ Name: "Instagram"})
    fmt.Println(slice[0].Name) // -> Instagram
    //
    // オブジェクトタイプからスライスを生成する
    //
    sliceType = reflect.SliceOf(objectType)
    slicePointer = reflect.New(sliceType).Interface().(*[]Sns)
    slice = *slicePointer
    slice = append(slice, Sns{ Name: "Qiita"})
    fmt.Println(slice[0].Name) // -> Qiita
}

こういう時の解説は

object := Sns{}

のようにせず、varで明示的に変数型宣言するとわかりやすい。

まず単一のオブジェクトのリフレクションからおさらい。

    object = Sns{}
    objectType = reflect.TypeOf(object)
    objectValue = reflect.New(objectType)
    objectPointer = objectValue.Interface().(*Sns)
    objectPointer.Name = "Twitter"
    fmt.Println(objectPointer.Name) // -> Twitter
  • reflect.TypeOfでオブジェクトのType型を取得できます。
  • Type型があればリフレクションでNewできます。
  • Newで得られるのはinterfaceではなく、Value型であることに注意!
  • interfaceを得る時は、Value#Interface()を使います
  • Interface()メソッドがオブジェクトのポインタを返すので、それを*Snsにキャストしてます

次に本題のスライス。Newで生成する場合

    //
    // Newで生成
    //
    sliceType = reflect.TypeOf([]Sns{})
    sliceValue = reflect.New(sliceType)
    slicePointerInterface = sliceValue.Interface()
    slicePointer = slicePointerInterface.(*[]Sns)
    slice = append(*slicePointer, Sns{ Name: "Facebook"})
    fmt.Println(slice[0].Name) // -> Facebook
  • sliceTypeにSnsのスライスのTypeを入れます
  • sliceTypeからリフレクションでNewするとValue型で得られる
  • ValueのInterface()はスライスのポインタを返す
  • よって*[]SnsでキャストすることでSnsスライスのポインタが得られる

Makeの場合。

    //
    // MakeSliceで生成
    //
    sliceValue = reflect.MakeSlice(sliceType, 0, 0)
    sliceInterface = sliceValue.Interface()
    slice = sliceInterface.([]Sns)
    slice = append(slice, Sns{ Name: "Instagram"})
    fmt.Println(slice[0].Name) // -> Instagram
  • Newの代わりにMakeSliceを使うとValueで得られる
  • この時のValueのInterface()は、スライスそのものを得られる
  • よって[]Snsでキャストすることで実体を得られる

オブジェクトタイプからスライスを生成する

    //
    // オブジェクトタイプからスライスを生成する
    //
    sliceType = reflect.SliceOf(objectType)
    slicePointer = reflect.New(sliceType).Interface().(*[]Sns)
    slice = *slicePointer
    slice = append(slice, Sns{ Name: "Qiita"})
    fmt.Println(slice[0].Name) // -> Qiita
  • SliceOfを使うと、その方のスライス型をTypeで得られる
  • New -> Interface()でスライスを生成しポインタを得る(MakeSliceの場合はスライスの実体)

オブジェクトのポインタから、そのタイプのスライスを生成する

実際、汎用的なスライス生成が必要なコードはこのようにすると思います。

func MakeSliceFromObjectPointer(objectPointer interface{}) interface{} {
    var object interface{}
    var objectType reflect.Type
    var sliceType reflect.Type
    var sliceValue reflect.Value

    // ValueOfでポインタをValue型を得て、さらにElem() -> Interface()でオブジェクトのinterfaceを得る
    object = reflect.ValueOf(objectPointer).Elem().Interface()
    // あとは同じ
    objectType = reflect.TypeOf(object)
    sliceType = reflect.SliceOf(objectType)
    sliceValue = reflect.MakeSlice(sliceType, 0, 0)
    return sliceValue.Interface()
}

sliceInterface := MakeSliceFromObjectPointer(&Sns{})
slice = sliceInterface.([]Sns)
slice = append(slice, Sns{ Name: "Stackoverflow"})
fmt.Println(slice[0].Name) // -> Stackoverflow

これで、Type型さえ渡せば、オブジェクト生成やスライス生成を自動生成する汎用コードが書けるはずです。

12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8