(免责声明:我不是C#专家)
Go的参数多态性与C#或Java中泛型的实现之间的一个显著区别是Go(仍然)没有针对类型参数的协变/反变的语法.
例如,在C#中,可以有实现IComparer<T>
和传递派生容器类的代码;或者在Java中,流API中典型的Predicate<? super T>
.在Go中,类型必须完全匹配,并且用不同的类型参数实例化泛型类型会产生不同的命名类型,而这些类型不能相互分配.另见:Why does Go not allow assigning one generic to another?
Go也不是OO,所以没有继承的概念.您可能有实现接口的类型,甚至参数化接口.一个人为的例子:
type Equaler[T any] interface {
Equals(T) bool
}
type Vector []int32
func (v Vector) Equals(other Vector) bool {
// some logic
}
用这段代码,Vector
实现Equaler
中的a specific instance,也就是Equaler[Vector]
.需要说明的是,以下var声明汇编:
var _ Equaler[Vector] = Vector{}
有了这个,你可以在T
中编写泛型函数,并使用T
来实例化Equaler
,你将能够传递任何实现Equaler
的特定实例的东西:
func Remove[E Equaler[T], T any](es []E, v T) []E {
for i, e := range es {
if e.Equals(v) {
return append(es[:i], es[i+1:]...)
}
}
return es
}
你可以用任何T
调用这个函数,因此用任何T
调用Equals(T)
方法:
// some other random type that implements Equaler[T]
type MyString string
// implements Equaler[string]
func (s MyString) Equals(other string) bool {
return strings.Split(string(s), "-")[0] == other
}
func main() {
vecs := []Vector{{1, 2}, {3, 4, 5}, {6, 7}, {8}}
fmt.Println(Remove(vecs, Vector{6, 7}))
// prints [[1 2] [3 4 5] [8]]
strs := []MyString{"foo-bar", "hello-world", "bar-baz"}
fmt.Println(Remove(strs, "hello"))
// prints [foo-bar bar-baz]
}
唯一的问题是,只有已定义的类型才能有方法,因此这种方法已经排除了所有未命名的复合类型.
然而,部分地说,Go具有更高阶的函数,因此使用该函数和非命名类型编写类似流的API并非不可能,例如:
func Where[C ~[]T, T any](collection C, predicate func(T) bool) (out C) {
for _, v := range collection {
if predicate(v) {
out = append(out, v)
}
}
return
}
func main() {
// vecs declared earlier
filtered := Where(vecs, func(v Vector) bool { return v[0] == 3})
fmt.Printf("%T %v", filtered, filtered)
// prints []main.Vector [[3 4 5]]
}
特别是在这里,您使用命名类型参数C ~[]T
,而不是仅定义collection []T
,以便可以将其用于both个命名和非命名类型.
操场上的可用代码:https://gotipplay.golang.org/p/mCM2TJ9qb3F
( Select 参数化接口与高阶函数可能取决于是否要链接方法,但Go中的方法链接一开始并不常见.)
Conclusion:这是否足以模仿LINQ或流式API,和/或启用大型通用库,只有实践才能说明.现有的功能非常强大,在Go 1.19中,在语言设计师获得了泛型的实际使用经验之后,可能会变得更加强大.