英文:
Identify non builtin-types using reflect
问题
我需要区分以下类型:
type A []byte
与 []byte
。使用 reflect
,reflect.TypeOf(A{}).Kind
告诉我它是一个 Slice
类型的 byte
。如何区分 []byte{}
和 A{}
,而不需要列出所有可能的类型进行检查?
在较新的 Go 版本中是否有新的方法来实现这个?
英文:
I need to differentiate such types as
type A []byte
from a []byte
. Using reflect
, reflect.TypeOf(A{}).Kind
tells me that it is a Slice
of byte
. How can I differentiate []byte{}
from A{}
, without having a bounded list of types to check for?
Are there new ways to do it in newer versions of Go?
答案1
得分: 17
一些背景信息
首先,让我们澄清一些与类型相关的事情。引用自规范:类型
类型确定了特定类型的值和操作的集合。类型可以是_命名的_或_未命名的_。命名类型由(可能是限定的)类型名称指定;未命名类型使用_类型字面量_指定,它从现有类型中组成一个新类型。
因此,有(预声明的)命名类型,如string
、int
等,你还可以使用类型声明(涉及type
关键字)创建新的命名类型,例如type MyInt int
。还有_未命名类型_,它是应用于/包含命名或未命名类型的_类型字面量_的结果,例如[]int
、struct{i int}
、*int
等。
你可以使用Type.Name()
方法获取命名类型的名称,它“对于未命名类型返回空字符串”:
var i int = 2
fmt.Printf("%q\n", reflect.TypeOf("abc").Name()) // 命名类型: "string"
fmt.Printf("%q\n", reflect.TypeOf(int(2)).Name()) // 命名类型: "int"
fmt.Printf("%q\n", reflect.TypeOf([]int{}).Name()) // 未命名类型: ""
fmt.Printf("%q\n", reflect.TypeOf(struct{ i int }{}).Name()) // 未命名类型: ""
fmt.Printf("%q\n", reflect.TypeOf(&struct{ i int }{}).Name()) // 未命名类型: ""
fmt.Printf("%q\n", reflect.TypeOf(&i).Name()) // 未命名类型: ""
有一些类型是预声明的,可以直接使用它们(作为原样或在类型字面量中使用):
布尔、数值和字符串类型的命名实例是预声明的。可以使用类型字面量构造复合类型——数组、结构体、指针、函数、接口、切片、映射和通道类型。
预声明的类型有:
bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
你可以使用Type.PkgPath()
来获取_命名_类型的包路径,如果该类型是预声明的(string
、error
)或未命名的(*T
、struct{}
、[]int
),包路径将为空字符串:
fmt.Printf("%q\n", reflect.TypeOf("abc").PkgPath()) // 预声明的: ""
fmt.Printf("%q\n", reflect.TypeOf(A{}).PkgPath()) // 命名类型: "main"
fmt.Printf("%q\n", reflect.TypeOf([]byte{}).PkgPath()) // 未命名类型: ""
因此,你有两个工具可用:Type.Name()
用于判断类型是否为_命名_类型,Type.PkgPath()
用于判断类型是否不是预声明的且为命名类型。
但是必须小心。如果你在类型字面量中使用自己的命名类型来构造一个新类型(例如[]A
),那将是一个未命名类型(如果不使用type
关键字来构造一个新的命名类型):
type ASlice []A
fmt.Printf("%q\n", reflect.TypeOf([]A{}).PkgPath()) // 也是未命名类型: ""
fmt.Printf("%q\n", reflect.TypeOf(ASlice{}).PkgPath()) // 命名类型: "main"
在这种情况下,你可以使用Type.Elem()
来获取类型的元素类型,如果类型的Kind
是Array
、Chan
、Map
、Ptr
或Slice
(否则Type.Elem()
会引发恐慌):
fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().Name()) // 元素类型: "A"
fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().PkgPath()) // 它是命名的,所以: "main"
总结
Type.PkgPath()
可以用来“过滤掉”预声明和未命名的类型。如果PkgPath()
返回一个非空字符串,你可以确定它是一个“自定义”类型。如果它返回一个空字符串,它仍然可能是一个未命名类型(在这种情况下,Type.Name()
返回""
),它是由一个“自定义”类型构造的;对于这种情况,你可以使用Type.Elem()
来查看它是否是由一个“自定义”类型构造的,这可能需要递归应用:
// [][]A -> Elem() -> []A 仍然是未命名类型: ""
fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().PkgPath())
// [][]A -> Elem() -> []A -> Elem() -> A 是命名的: "main"
fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().Elem().PkgPath())
在Go Playground上尝试所有示例。
特殊情况#1:匿名结构体类型
还有一种情况是匿名结构体类型,它是未命名的,但它可能有一个字段是“自定义”类型。可以通过迭代结构体类型的字段并对每个字段执行相同的检查来处理此情况,如果发现任何字段是“自定义”类型,我们可以认为整个结构体类型是“自定义”的。
特殊情况#2:映射类型
对于映射,如果其键或值类型中的任何一个是“自定义”类型,我们可以将未命名的映射类型视为“自定义”。
可以使用上述提到的Type.Elem()
方法查询映射的值类型,可以使用Type.Key()
方法查询映射的键类型 - 在映射的情况下,我们还必须检查这一点。
示例实现
func isCustom(t reflect.Type) bool {
if t.PkgPath() != "" {
return true
}
if k := t.Kind(); k == reflect.Array || k == reflect.Chan || k == reflect.Map ||
k == reflect.Ptr || k == reflect.Slice {
return isCustom(t.Elem()) || k == reflect.Map && isCustom(t.Key())
} else if k == reflect.Struct {
for i := t.NumField() - 1; i >= 0; i-- {
if isCustom(t.Field(i).Type) {
return true
}
}
}
return false
}
测试它(在Go Playground上尝试):
type K int
var i int = 2
fmt.Println(isCustom(reflect.TypeOf(""))) // false
fmt.Println(isCustom(reflect.TypeOf(int(2)))) // false
fmt.Println(isCustom(reflect.TypeOf([]int{}))) // false
fmt.Println(isCustom(reflect.TypeOf(struct{ i int }{}))) // false
fmt.Println(isCustom(reflect.TypeOf(&i))) // false
fmt.Println(isCustom(reflect.TypeOf(map[string]int{}))) // false
fmt.Println(isCustom(reflect.TypeOf(A{}))) // true
fmt.Println(isCustom(reflect.TypeOf(&A{}))) // true
fmt.Println(isCustom(reflect.TypeOf([]A{}))) // true
fmt.Println(isCustom(reflect.TypeOf([][]A{}))) // true
fmt.Println(isCustom(reflect.TypeOf(struct{ a A }{}))) // true
fmt.Println(isCustom(reflect.TypeOf(map[K]int{}))) // true
fmt.Println(isCustom(reflect.TypeOf(map[string]K{}))) // true
英文:
Some background
First let's clear some things related to types. Quoting from Spec: Types:
> A type determines the set of values and operations specific to values of that type. Types may be named or unnamed. Named types are specified by a (possibly qualified) type name; unnamed types are specified using a type literal, which composes a new type from existing types.
So there are (predeclared) named types such as string
, int
etc, and you may also create new named types using type declarations (which involves the type
keyword) such as type MyInt int
. And there are unnamed types which are the result of a type literal (applied to / including named or unnamed types) such as []int
, struct{i int}
, *int
etc.
You can get the name of a named type using the Type.Name()
method, which "returns an empty string for unnamed types":
var i int = 2
fmt.Printf("%q\n", reflect.TypeOf("abc").Name()) // Named: "string"
fmt.Printf("%q\n", reflect.TypeOf(int(2)).Name()) // Named: "int"
fmt.Printf("%q\n", reflect.TypeOf([]int{}).Name()) // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(struct{ i int }{}).Name()) // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(&struct{ i int }{}).Name()) // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(&i).Name()) // Unnamed: ""
There are types which are predeclared and are ready for you to use them (either as-is, or in type literals):
> Named instances of the boolean, numeric, and string types are predeclared. Composite types—array, struct, pointer, function, interface, slice, map, and channel types—may be constructed using type literals.
Predeclared types are:
bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
You may use Type.PkgPath()
to get a named type's package path, which "if the type was predeclared (string
, error
) or unnamed (*T
, struct{}
, []int
), the package path will be the empty string":
fmt.Printf("%q\n", reflect.TypeOf("abc").PkgPath()) // Predeclared: ""
fmt.Printf("%q\n", reflect.TypeOf(A{}).PkgPath()) // Named: "main"
fmt.Printf("%q\n", reflect.TypeOf([]byte{}).PkgPath()) // Unnamed: ""
So you have 2 tools available to you: Type.Name()
to tell if the type is a named type, and Type.PkgPath()
to tell if the type is not predeclared and is a named type.
But care must be taken. If you use your own, named type in a type literal to construct a new type (e.g. []A
), that will be an unnamed type (if you don't use the type
keyword to construct a new, named type):
type ASlice []A
fmt.Printf("%q\n", reflect.TypeOf([]A{}).PkgPath()) // Also unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(ASlice{}).PkgPath()) // Named: "main"
What can you do in such cases? You may use Type.Elem()
to get the type's element type, if type's Kind
is Array
, Chan
, Map
, Ptr
, or Slice
(else Type.Elem()
panics):
fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().Name()) // Element type: "A"
fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().PkgPath()) // Which is named, so: "main"
Summary
Type.PkgPath()
can be used to "filter out" predeclared and unnamed types. If PkgPath()
returns a non-empty string, you can be sure it's a "custom" type. If it returns an empty string, it still may be an unnamed type (in which case Type.Name()
returns ""
) constructed from a "custom" type; for that you may use Type.Elem()
to see if it is constructed from a "custom" type, which may have to be applied recursively:
// [][]A -> Elem() -> []A which is still unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().PkgPath())
// [][]A -> Elem() -> []A -> Elem() -> A which is named: "main"
fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().Elem().PkgPath())
Try all the examples on the Go Playground.
Special case #1: Anonymous struct types
There is also the case of an anonymous struct type which is unnamed, but it may have a field of a "custom" type. This case can be handled by iterating over the fields of the struct type and performing the same check on each field, and if any of them is found to be a "custom" type, we can claim the whole struct type to be "custom".
Special case #2: Map types
In case of maps we may consider an unnamed map type "custom" if any of its key or value type is "custom".
The value type of a map can be queried with the above mentioned Type.Elem()
method, and the key type of a map can be queried with the Type.Key()
method - we also have to check this in case of maps.
Example implementation
func isCustom(t reflect.Type) bool {
if t.PkgPath() != "" {
return true
}
if k := t.Kind(); k == reflect.Array || k == reflect.Chan || k == reflect.Map ||
k == reflect.Ptr || k == reflect.Slice {
return isCustom(t.Elem()) || k == reflect.Map && isCustom(t.Key())
} else if k == reflect.Struct {
for i := t.NumField() - 1; i >= 0; i-- {
if isCustom(t.Field(i).Type) {
return true
}
}
}
return false
}
Testing it (try it on the Go Playground):
type K int
var i int = 2
fmt.Println(isCustom(reflect.TypeOf(""))) // false
fmt.Println(isCustom(reflect.TypeOf(int(2)))) // false
fmt.Println(isCustom(reflect.TypeOf([]int{}))) // false
fmt.Println(isCustom(reflect.TypeOf(struct{ i int }{}))) // false
fmt.Println(isCustom(reflect.TypeOf(&i))) // false
fmt.Println(isCustom(reflect.TypeOf(map[string]int{}))) // false
fmt.Println(isCustom(reflect.TypeOf(A{}))) // true
fmt.Println(isCustom(reflect.TypeOf(&A{}))) // true
fmt.Println(isCustom(reflect.TypeOf([]A{}))) // true
fmt.Println(isCustom(reflect.TypeOf([][]A{}))) // true
fmt.Println(isCustom(reflect.TypeOf(struct{ a A }{}))) // true
fmt.Println(isCustom(reflect.TypeOf(map[K]int{}))) // true
fmt.Println(isCustom(reflect.TypeOf(map[string]K{}))) // true
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论