从泛型结构中使用反射获取类型参数。

huangapple go评论82阅读模式
英文:

Get type parameter from a generic struct using reflection

问题

假设我有以下结构体:

type MyGeneric[T string | int] struct {
}

我想要检查在创建一个新的MyGeneric实例时,用于实例化该结构体的泛型是字符串还是整数。

myGenericString := MyGeneric[string]{}
myGenericString.canHandle("hello") -> 应该返回true
myGenericString.canHandle(8) -> 应该返回false

func (mG MyGeneric[T]) canHandle(value any) bool {
    // 如何获取T与value的类型相同
}
英文:

Imagine I have the following struct:

type MyGeneric[T string | int] struct {
}

I want to check whether the generic used to instantiate that struct was a string or a int when creating a new MyGeneric.

myGenericString := MyGeneric[string]{}
myGenericString.canHandle("hello") -> should return true
myGenericString.canHandle(8) -> should return false

func (mG MyGeneric[T]) canHandle(value any) bool {
    // how to get what T is the same type as value
}

答案1

得分: 9

只需直接实例化T以获取其值。

type MyGeneric[T any] struct {
}

func (mG MyGeneric[T]) canHandle(value any) bool {
	var t T
	tt := reflect.TypeOf(t)

	vt := reflect.TypeOf(value)

	fmt.Printf("-> %v == %v\n", tt, vt)
	return tt == vt
}

type empty struct{}

func main() {
	fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(""))
	fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(1))
	fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[struct{}]{}))
	fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[empty]{}))
}

输出:

-> string == string
true
-> string == int
false
-> main.MyGeneric[struct {}] == main.MyGeneric[struct {}]
true
-> main.MyGeneric[struct {}] == main.MyGeneric[main.empty]
false

如果你担心T会分配太多未使用的堆栈空间,可以将其改为数组形式:

var tArr [0]T
tt := reflect.TypeOf(tArr).Elem()
英文:

Just instantiate the T directly to get its value.

type MyGeneric[T any] struct {
}

func (mG MyGeneric[T]) canHandle(value any) bool {
	var t T
	tt := reflect.TypeOf(t)

	vt := reflect.TypeOf(value)

	fmt.Printf("-> %v == %v\n", tt, vt)
	return tt == vt
}

type empty struct{}

func main() {
	fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(""))
	fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(1))
	fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[struct{}]{}))
	fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[empty]{}))
}

Output:

-> string == string
true
-> string == int
false
-> main.MyGeneric[struct {}] == main.MyGeneric[struct {}]
true
-> main.MyGeneric[struct {}] == main.MyGeneric[main.empty]
false

If you are worried about T allocating too much unused stack, make it an array instead:

var tArr [0]T
tt := reflect.TypeOf(tArr).Elem()

答案2

得分: 3

这还没有被实现。关于向reflect.Type添加必要方法的提案目前还是一个开放提案

截至Go 1.19,目前的解决方法是解析从TypeOf获得的字符串。类似这样:

var r = regexp.MustCompile("[A-Za-z0-9_]+\\.[A-Za-z0-9_]+\\[(.*)\\]")

func (mG MyGeneric[T]) typeParam(value any) {
    tname := reflect.TypeOf(mG).String() // 这是 `main.MyGeneric[string]`
    match := r.FindStringSubmatch(tname)
    fmt.Println(match[1]) // string
}

如果目标只是获取类型参数的名称,这个方法还不错,因为它依赖于类型的字符串表示。好的一面是,它不会让你考虑如果使用接口实例化T会发生什么。

如果你需要对T的类型进行进一步的计算,例如与其他类型进行比较等,@SOFe的回答提供了一个不依赖于任意字符串表示的解决方案。

但是要注意使用接口实例化的T:参见https://stackoverflow.com/questions/74000242/in-golang-how-to-compare-interface-as-generics-type-to-nil

英文:

It hasn't been implemented yet. There is an open proposal about adding the necessary methods to reflect.Type.

The current workaround as of Go 1.19 is to parse the string obtained from TypeOf. Something like this:

var r = regexp.MustCompile("[A-Za-z0-9_]+\\.[A-Za-z0-9_]+\\[(.*)\\]")

func (mG MyGeneric[T]) typeParam(value any) {
    tname := reflect.TypeOf(mG).String() // this is `main.MyGeneric[string]`
	match := r.FindStringSubmatch(tname)
	fmt.Println(match[1]) // string
}

This if the goal is just to obtain the name of the type parameter. It's not great, as it depends on the type's string representation. On the bright side, it doesn't force you to think about what happens if you instantiate T with interfaces.

If you need to do further computations with the type of T, e.g. compare it to other types etc. @SOFe’s answer provides a solution that doesn’t depend on arbitrary string representations.

However watch out for T instantiated with interfaces: see also https://stackoverflow.com/questions/74000242/in-golang-how-to-compare-interface-as-generics-type-to-nil

答案3

得分: 2

另一种解决方案是在结构体中添加一个placeHolder字段,可以通过类型切换来获取其类型,以避免使用反射:

type MyGeneric[T string | int] struct {
  placeHolder T
}

func (mG MyGeneric[T]) canHandle(value any) bool {
    switch t1 := any(p.placeHolder).(type) {
	case any:
		switch t2 := value.(type) {
		case any:
			return t1 == t2
		}
	}
	return false
}
英文:

Another solution is to add a placeHolder field to your struct, that can be used to get its type via type switch to avoid reflect:

type MyGeneric[T string | int] struct {
  placeHolder T
}

func (mG MyGeneric[T]) canHandle(value any) bool {
    switch t1 := any(p.placeHolder).(type) {
	case any:
		switch t2 := value.(type) {
		case any:
			return t1 == t2
		}
	}
	return false
}

huangapple
  • 本文由 发表于 2022年9月27日 16:45:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/73864711.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定