![](/img/trans.png)
[英]C++ switch statement with case values of reinterpret_cast(string)
[英]Generic type in a switch statement
刚开始学习generics。 我正在制作一个命令处理器,老实说,我不知道如何表达,所以我将展示一个示例问题:
var ErrInvalidCommand = errors.New("invalid command")
type TransactionalFn[T any] func(ctx context.Context, db T) error
func NewTransactionalCommand[T any](fn TransactionalFn[T]) *TransactionalCommand[T] {
return &TransactionalCommand[T]{
fn: fn,
}
}
type TransactionalCommand[T any] struct {
fn TransactionalFn[T]
}
func (cmd *TransactionalCommand[T]) StartTransaction() error {
return nil
}
func (cmd *TransactionalCommand[T]) Commit() error {
return nil
}
func (cmd *TransactionalCommand[T]) Rollback() error {
return nil
}
type CMD interface{}
type CommandManager struct{}
func (m *CommandManager) Handle(ctx context.Context, cmd CMD) error {
switch t := cmd.(type) {
case *TransactionalCommand[any]:
return m.handleTransactionalCommand(ctx, t)
default:
fmt.Printf("%T\n", cmd)
return ErrInvalidCommand
}
}
func (m *CommandManager) handleTransactionalCommand(ctx context.Context, cmd *TransactionalCommand[any]) error {
if err := cmd.StartTransaction(); err != nil {
return err
}
if err := cmd.fn(ctx, nil); err != nil {
if err := cmd.Rollback(); err != nil {
return err
}
}
if err := cmd.Commit(); err != nil {
return err
}
return nil
}
// tests
type db struct{}
func (*db) Do() {
fmt.Println("doing stuff")
}
func TestCMD(t *testing.T) {
ctx := context.Background()
fn := func(ctx context.Context, db *db) error {
fmt.Println("test cmd")
db.Do()
return nil
}
tFn := bus.NewTransactionalCommand(fn)
mng := &bus.CommandManager{}
err := mng.Handle(ctx, tFn)
if err != nil {
t.Fatal(err)
}
}
mng.handle
返回ErrInvalidCommand
因此测试失败,因为cmd
是*TransactionalCommand[*db]
而不是*TransactionalCommand[any]
让我再举一个更抽象的例子:
type A[T any] struct{}
func (*A[T]) DoA() { fmt.Println("do A") }
type B[T any] struct{}
func (*B[T]) DoB() { fmt.Println("do B") }
func Handle(s interface{}) {
switch x := s.(type) {
case *A[any]:
x.DoA()
case *B[any]:
x.DoB()
default:
fmt.Printf("%T\n", s)
}
}
func TestFuncSwitch(t *testing.T) {
i := &A[int]{}
Handle(i) // expected to print "do A"
}
为什么这个 switch 语句 case *A[any]
不匹配*A[int]
? 如何使CommandManager.Handle(...)
接受通用命令?
*A[any]
不匹配*A[int]
因为any
是 static 类型,而不是通配符。 因此,实例化具有不同类型的通用结构会产生不同的类型。
为了正确匹配类型开关中的泛型结构,您必须使用类型参数对其进行实例化:
func Handle[T any](s interface{}) {
switch x := any(s).(type) {
case *A[T]:
x.DoA()
case *B[T]:
x.DoB()
default:
panic("no match")
}
}
尽管在没有其他 function arguments 来推断T
的情况下,您将不得不使用显式实例化调用Handle
。 T
不会单独从结构中推断出来。
func main() {
i := &A[int]{}
Handle[int](i) // expected to print "do A"
}
游乐场: https://go.dev/play/p/2e5E9LSWPmk
但是,当Handle
实际上是一个方法时,如在您的数据库代码中,这具有在实例化接收器时选择类型参数的缺点。
为了改进这里的代码,您可以将Handle
设置为顶级 function:
func Handle[T any](ctx context.Context, cmd CMD) error {
switch t := cmd.(type) {
case *TransactionalCommand[T]:
return handleTransactionalCommand(ctx, t)
default:
fmt.Printf("%T\n", cmd)
return ErrInvalidCommand
}
}
然后你就有了如何将参数db T
提供给命令 function 的问题。 为此,您可以:
只需将额外的*db
参数传递给Handle
和handleTransactionalCommand
,这也有助于类型参数推断。 调用为Handle(ctx, &db{}, tFn)
。 游乐场: https://go.dev/play/p/6WESb86KN5D
传递CommandManager
的实例(如上面的解决方案,但*db
已包装)。 更加冗长,因为它需要在任何地方进行显式实例化。 游乐场: https://go.dev/play/p/SpXczsUM5aW
改用参数化接口(如下所示)。 因此,您甚至不必进行类型切换。 游乐场: https://go.dev/play/p/EgULEIL6AV5
type CMD[T any] interface {
Exec(ctx context.Context, db T) error
}
为什么泛型类型开关无法编译?
这实际上是 Go 团队有意决定的结果。 事实证明,允许在参数化类型上进行类型切换会导致混淆
在此设计的早期版本中,我们允许在类型为类型参数或类型基于类型参数的变量上使用类型断言和类型切换。 我们删除了这个工具,因为总是可以将任何类型的值转换为空接口类型,然后在其上使用类型断言或类型开关。 此外,有时令人困惑的是,在具有使用近似元素的类型集的约束中,类型断言或类型切换将使用实际的类型参数,而不是类型参数的基础类型(差异在关于识别的部分中解释匹配的预声明类型)
来自类型参数提案
让我把强调的语句变成代码。 如果类型约束使用类型近似(注意波浪线)...
func PrintStringOrInt[T ~string | ~int](v T)
...如果还有一个以int
作为基础类型的自定义类型...
type Seconds int
...如果PrintOrString()
使用Seconds
参数调用...
PrintStringOrInt(Seconds(42))
...然后switch
块不会进入int case
,而是 go 进入default case
,因为Seconds
不是int
。 开发人员可能期望case int:
也匹配Seconds
类型。
要允许case
语句同时匹配Seconds
和int
将需要新的语法,例如,
case ~int:
在撰写本文时,讨论仍处于开放状态,也许它会带来一个全新的选项来打开类型参数(例如, switch type T
)。
更多细节请参考提案:spec: generics: type switch on parametric types
技巧:将类型转换为 'any'
幸运的是,我们不需要等待该提案在未来的版本中实施。 现在有一个超级简单的解决方法。
不要打开v.(type)
,而是打开any(v).(type)
。
switch any(v).(type) {
...
这个技巧将v
转换为一个空的interface{}
(又名any
), switch
很乐意为此进行类型匹配。
资料来源: 使用 generics 时的提示和技巧
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.