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

问题描述 投票:0回答:3

想象我有以下结构:

type MyGeneric[T string | int] struct {
}

我想在创建新的 MyGeneric 时检查用于实例化该结构的泛型是字符串还是 int。

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
}

go generics reflection
3个回答
10
投票

对于普通值,只需直接实例化

T
即可获取其值并
reflect.TypeOf()
。但我们可以声明一个
[0]T
并采用它的元素类型,因为:

  • [0]T
    占用0内存,而
    T
    可能与
    T
    一样大,如果T类似于
    [4096]int
    ,这会浪费大量堆栈和堆
    1
  • 原始
    T
    不适用于接口类型。
    reflect.TypeOf()
    在nil接口值(无论是否为空接口)上将返回
    reflect.Type(nil)
    ,后续使用将导致恐慌。
package main

import (
    "fmt"
    "reflect"
)

type MyGeneric[T any] struct {
}

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

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

type empty struct{}

func main() {
    fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(""))
    fmt.Printf("%v\n", MyGeneric[any]{}.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 covers string
true
-> interface {} covers string
true
-> string covers int
false
-> main.MyGeneric[struct {}] covers main.MyGeneric[struct {}]
true
-> main.MyGeneric[struct {}] covers main.MyGeneric[main.empty]
false

1 不确定它是否可以优化,或者是否在堆或堆栈或两者上分配,因为它在向上转换为

reflect.TypeOf
后被传递到
interface{}


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() // this is `main.MyGeneric[string]`
    match := r.FindStringSubmatch(tname)
    fmt.Println(match[1]) // string
}

如果目标只是获取类型参数的名称。这不太好,因为它取决于类型的字符串表示形式。从好的方面来说,它不会强迫您思考如果使用接口实例化

T
会发生什么。

如果您需要使用

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

但是要注意用接口实例化的

T
:另请参阅在Golang中,如何将作为泛型类型的接口与nil进行比较?


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
}
© www.soinside.com 2019 - 2024. All rights reserved.