オブジェクトのタイプを渡すと、そのスライスをプログラムで作る汎用処理的なコードを書きたい時がある。結構ややこしくてハマったので、同じようにハマってる人のために書き記しておきます。
実行環境は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型さえ渡せば、オブジェクト生成やスライス生成を自動生成する汎用コードが書けるはずです。