给出一个结构:
type MyStruct struct {
A int
B int
}
和一个结构名称为
的字符串a := "MyStruct"
或
a := "mypkg.MyStruct"
如何从字符串名称而不是结构创建结构的实例?我的想法是,我将创建一个应用程序,其中所有结构都链接到二进制文件中,但是从字符串创建运行时实例。 (一种元元)
答案 0 :(得分:63)
Go中没有类型的中央注册表,因此在一般情况下你所要求的是不可能的。
您可以手动构建自己的注册表,以使用从字符串到每种类型对应的reflect.Type
值的映射来支持此类功能。例如:
var typeRegistry = make(map[string]reflect.Type)
func init() {
myTypes := []interface{}{MyString{}}
for _, v := range myTypes {
// typeRegistry["MyString"] = reflect.TypeOf(MyString{})
typeRegistry[fmt.Sprintf("%T", v)] = reflect.TypeOf(v)
}
}
然后您可以创建类型的实例,如下所示:
func makeInstance(name string) interface{} {
v := reflect.New(typeRegistry[name]).Elem()
// Maybe fill in fields here if necessary
return v.Interface()
}
答案 1 :(得分:8)
您可以创建名称地图 - > struct“template”
从地图中获取值时,您会获得该值的副本,该地图有效地充当您的值的工厂。
请注意,地图中的值是唯一的。 为了实际对结构做一些事情,你需要断言它的类型或使用一些基于反射的处理器(即:从map获取struct,然后json解码到struct)
这是一个简单的Example,其中一个原始形式的结构和一个预先填充的结构。 注意foowv1上的类型断言,这样我实际上可以设置值。
package main
import "fmt"
type foo struct {
a int
}
var factory map[string]interface{} = map[string]interface{}{
"foo": foo{},
"foo.with.val": foo{2},
}
func main() {
foo1 := factory["foo"]
foo2 := factory["foo"]
fmt.Println("foo1", &foo1, foo1)
fmt.Println("foo2", &foo2, foo2)
foowv1 := factory["foo.with.val"].(foo)
foowv1.a = 123
foowv2 := factory["foo.with.val"]
fmt.Println("foowv1", &foowv1, foowv1)
fmt.Println("foowv2", &foowv2, foowv2)
}
答案 2 :(得分:5)
Go运行时不会公开程序中构建的类型列表。并且有一个原因:您永远不必能够构建所有可用类型,而只需构建一个子集。
您可以使用地图为自己构建此子集。您可以使用reflect
包从reflect.Type
创建实例。
我的解决方案(see on Go Playground)使用类型化的nil指针(而不是空值)来减少构建地图时的分配大小(与@james-henstridge解决方案相比)。
package main
import (
"fmt"
"reflect"
)
var typeRegistry = make(map[string]reflect.Type)
func registerType(typedNil interface{}) {
t := reflect.TypeOf(typedNil).Elem()
typeRegistry[t.PkgPath() + "." + t.Name()] = t
}
type MyString string
type myString string
func init() {
registerType((*MyString)(nil))
registerType((*myString)(nil))
// ...
}
func makeInstance(name string) interface{} {
return reflect.New(typeRegistry[name]).Elem().Interface()
}
func main() {
for k := range typeRegistry {
fmt.Println(k)
}
fmt.Printf("%T\n", makeInstance("main.MyString"))
fmt.Printf("%T\n", makeInstance("main.myString"))
}