Golang中如何检查泛型类型的接口实现
Golang中如何检查泛型类型的接口实现 我有一个函数需要比较两个泛型类型。如果它们实现了 Equal 方法,则应优先使用该方法。以下是我的代码:
type EqualChecker interface {
Equal(EqualChecker) bool
}
//AssertEq adds error to test with stack trace when provided arguments are not equal
//If values of Pointers should be compared use AssertEqPtr
func AssertEq[C comparable](left C, right C, t *testing.T) {
leftE, ok := left.(EqualChecker)
if ok {
rightE, _ := right.(EqualChecker)
if !leftE.Equal(rightE) {
t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
}
return
}
if left != right {
t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
}
}
但我遇到了编译错误:
- invalid operation: cannot use type assertion on type parameter value left (variable of type C constrained by comparable)
请问有什么办法可以解决这个问题吗?
更多关于Golang中如何检查泛型类型的接口实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中如何检查泛型类型的接口实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我对泛型一无所知,但类型断言仅在表达式为接口类型时才有效。例如,你不能这样做:
var a string = "test"
b := a.(interface{})
因为 a 是一个具体的 string 类型。也许问题在于类型约束中没有确保 C 是一个接口类型。再次声明,我对泛型一无所知,所以我不确定如何修复它。是否有约束要求类型必须是接口类型?
确实,我漏掉了那部分。
我短暂地考虑过使用联合类型,就像这样:
func AssertEq[T Equaler[T] | comparable](left, right T, t *testing.T) {
然而,声明了方法的接口不能作为联合类型的一部分。所以这行不通。
如果没有其他解决方案出现,我想你可能需要将反射作为最后的手段。(我想到了 reflect.DeepEqual。)
感谢您的详细回复,但遗憾的是,您的解决方案没有抓住我的核心要点。
我明确希望接受任何可比较的类型。如果该类型还实现了 Equal 方法,我希望能优先使用该方法。
例如:
- 我希望能够直接传递两个
int类型,并使用==或!=来比较它们。 - 如果我传递一个
Time类型,我希望检查日期是否相等,而不考虑时区。因此,我需要在那里使用.Equal而不是==或!=。
如果我想将函数的使用限制在仅实现了 Equal 的类型上,您的解决方案是可行的。
如果类型限制不那么严格,您可能会问在这个例子中使用泛型类型的目的是什么。原因是,只有当传递的两个参数具有相同类型并且能以某种方式进行比较时,这个函数才有意义。
好的,我之前的回答过于仓促,未经深思熟虑就发布了。请忽略它。
这里有一个更好的解决方案,它 (a) 有效,并且 (b) 确实利用了类型参数(也称为泛型)。
代码解释如下:
- 创建一个参数化接口,这样我们就可以定义一个
Equal(T)函数,而无需命名其参数。
type Equaler[T any] interface {
Equal(T) bool
}
- 函数
AssertEq被参数化为AssertEq[T Equaler[T]],因为:- 类型约束是接口,因此我们可以在这里使用
Equaler,并且 Equaler接口本身是一个参数化接口,因此我们在这里使用Equaler[T]。
- 类型约束是接口,因此我们可以在这里使用
func AssertEq[T Equaler[T]](left, right T, t *testing.T) {
if !left.Equal(right) {
t.Errorf("Not equal:\nleft: %+v\nright: %+v\n", left, right)
}
return
}
- 示例结构体类型
AmIEqual通过定义一个Equal()方法来实现接口Equaler[T]。
type AmIEqual struct {
value int
}
func (e AmIEqual) Equal(other AmIEqual) bool {
return e.value == other.value
}
- 现在我们可以实例化
AmIEqual值并将其传递给AssertEq()。 得益于类型推断,无需像AssertEq[AmIEqual](a, b, t)这样调用AssertEq,尽管后者也同样有效。
func TestAssert(t *testing.T) {
a := AmIEqual{value: 1}
b := AmIEqual{value: 1}
AssertEq[AmIEqual](a, b, t) // PASS
// 注释掉下面两行以获得 PASS 结果
c := AmIEqual{value: 2}
AssertEq(a, c, t) // FAIL
}
在Go中,类型参数不能直接使用类型断言。你需要使用类型约束来检查泛型类型是否实现了特定接口。以下是修改后的代码:
import (
"runtime/debug"
"testing"
)
type EqualChecker interface {
Equal(EqualChecker) bool
}
// EqualComparable 约束类型必须实现 EqualChecker 接口或是 comparable 类型
type EqualComparable interface {
EqualChecker | comparable
}
// AssertEq 比较两个泛型类型的值
func AssertEq[T EqualComparable](left, right T, t *testing.T) {
// 尝试将 left 转换为 EqualChecker 接口
if eq, ok := any(left).(EqualChecker); ok {
// 如果 left 实现了 EqualChecker,那么 right 也应该实现
if eq2, ok2 := any(right).(EqualChecker); ok2 {
if !eq.Equal(eq2) {
t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
}
return
}
}
// 回退到 comparable 比较
if left != right {
t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
}
}
或者,使用更灵活的类型约束方法:
import (
"runtime/debug"
"testing"
)
type EqualChecker interface {
Equal(EqualChecker) bool
}
// EqualComparable 使用类型集约束
type EqualComparable interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string |
~bool |
~complex64 | ~complex128 |
EqualChecker
}
// AssertEq 比较两个泛型类型的值
func AssertEq[T EqualComparable](left, right T, t *testing.T) {
// 通过 any 类型转换进行类型断言
if eq, ok := interface{}(left).(EqualChecker); ok {
if eq2, ok2 := interface{}(right).(EqualChecker); ok2 {
if !eq.Equal(eq2) {
t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
}
return
}
}
// 回退到 comparable 比较
if left != right {
t.Errorf("Not equal: \n left: %+v\nright: %+v\n%s", left, right, debug.Stack())
}
}
示例类型实现:
type Point struct {
X, Y int
}
func (p Point) Equal(other EqualChecker) bool {
if o, ok := other.(Point); ok {
return p.X == o.X && p.Y == o.Y
}
return false
}
// 使用示例
func TestAssertEq(t *testing.T) {
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 1, Y: 2}
p3 := Point{X: 1, Y: 3}
AssertEq(p1, p2, t) // 通过
AssertEq(p1, p3, t) // 失败
// 也可以用于基本类型
AssertEq(1, 1, t) // 通过
AssertEq(1, 2, t) // 失败
}
关键点:
- 使用
any(left)或interface{}(left)将泛型类型转换为接口类型 - 定义合适的类型约束来限制泛型类型范围
- 类型断言只能在接口类型上使用,不能直接在类型参数上使用

