简体   繁体   English

switch 语句中的泛型类型

[英]Generic type in a switch statement

Just started learning generics.刚开始学习generics。 I'm making a command processor and I honestly don't know how to word this so I'm just going to show an example problem:我正在制作一个命令处理器,老实说,我不知道如何表达,所以我将展示一个示例问题:

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 returns ErrInvalidCommand so the test fails because cmd is *TransactionalCommand[*db] and not *TransactionalCommand[any] mng.handle返回ErrInvalidCommand因此测试失败,因为cmd*TransactionalCommand[*db]而不是*TransactionalCommand[any]

Let me give another, more abstract example:让我再举一个更抽象的例子:

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"
}

Why doesn't this switch statement case *A[any] match *A[int] ?为什么这个 switch 语句 case *A[any]不匹配*A[int] How to make CommandManager.Handle(...) accept generic Commands?如何使CommandManager.Handle(...)接受通用命令?

*A[any] does not match *A[int] because any is a static type, not a wildcard. *A[any]不匹配*A[int]因为any是 static 类型,而不是通配符。 Therefore instantiating a generic struct with different types yields different types .因此,实例化具有不同类型的通用结构会产生不同的类型

In order to correctly match a generic struct in a type switch, you must instantiate it with a type parameter:为了正确匹配类型开关中的泛型结构,您必须使用类型参数对其进行实例化:

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")
    }
}

Though in absence of other function arguments to infer T , you will have to call Handle with explicit instantiation.尽管在没有其他 function arguments 来推断T的情况下,您将不得不使用显式实例化调用Handle T won't be inferred from the struct alone. T不会单独从结构中推断出来。

func main() {
    i := &A[int]{}
    Handle[int](i) // expected to print "do A"
}

Playground: https://go.dev/play/p/2e5E9LSWPmk游乐场: https://go.dev/play/p/2e5E9LSWPmk


However when Handle is actually a method, as in your database code, this has the drawback of choosing the type parameter when instantiating the receiver.但是,当Handle实际上是一个方法时,如在您的数据库代码中,这具有在实例化接收器时选择类型参数的缺点。

In order to improve the code here you can make Handle a top-level function:为了改进这里的代码,您可以将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
    }
}

Then you have the problem of how to supply the argument db T to the command function.然后你就有了如何将参数db T提供给命令 function 的问题。 For this, you might:为此,您可以:

  • simply pass an additional *db argument to Handle and handleTransactionalCommand , which also helps with type parameter inference.只需将额外的*db参数传递给HandlehandleTransactionalCommand ,这也有助于类型参数推断。 Call as Handle(ctx, &db{}, tFn) .调用为Handle(ctx, &db{}, tFn) Playground: https://go.dev/play/p/6WESb86KN5D游乐场: https://go.dev/play/p/6WESb86KN5D

  • pass an instance of CommandManager (like solution above but *db is wrapped).传递CommandManager的实例(如上面的解决方案,但*db已包装)。 Much more verbose, as it requires explicit instantiation everywhere.更加冗长,因为它需要在任何地方进行显式实例化。 Playground: https://go.dev/play/p/SpXczsUM5aW游乐场: https://go.dev/play/p/SpXczsUM5aW

  • use a parametrized interface instead (like below).改用参数化接口(如下所示)。 So you don't even have to type-switch.因此,您甚至不必进行类型切换。 Playground: https://go.dev/play/p/EgULEIL6AV5游乐场: https://go.dev/play/p/EgULEIL6AV5

type CMD[T any] interface {
    Exec(ctx context.Context, db T) error
}

Why does the generic type switch fail to compile?为什么泛型类型开关无法编译?

  • This is in fact the result of an intentional decision of the Go team.这实际上是 Go 团队有意决定的结果。 It turned out that allowing type switches on parametrized types can cause confusion事实证明,允许在参数化类型上进行类型切换会导致混淆

  • In an earlier version of this design, we permitted using type assertions and type switches on variables whose type was a type parameter, or whose type was based on a type parameter.在此设计的早期版本中,我们允许在类型为类型参数或类型基于类型参数的变量上使用类型断言和类型切换。 We removed this facility because it is always possible to convert a value of any type to the empty interface type, and then use a type assertion or type switch on that.我们删除了这个工具,因为总是可以将任何类型的值转换为空接口类型,然后在其上使用类型断言或类型开关。 Also, it was sometimes confusing that in a constraint with a type set that uses approximation elements, a type assertion or type switch would use the actual type argument, not the underlying type of the type argument (the difference is explained in the section on identifying the matched predeclared type)此外,有时令人困惑的是,在具有使用近似元素的类型集的约束中,类型断言或类型切换将使用实际的类型参数,而不是类型参数的基础类型(差异在关于识别的部分中解释匹配的预声明类型)

    From the Type Parameters Proposal来自类型参数提案

Let me turn the emphasized statement into code.让我把强调的语句变成代码。 If the type constraint uses type approximation (note the tildes)...如果类型约束使用类型近似(注意波浪线)...

func PrintStringOrInt[T ~string | ~int](v T)

...and if there also was a custom type with int as the underlying type... ...如果还有一个以int作为基础类型的自定义类型...

type Seconds int

...and if PrintOrString() is called with a Seconds parameter... ...如果PrintOrString()使用Seconds参数调用...

PrintStringOrInt(Seconds(42))

...then the switch block would not enter the int case but go right into the default case , because Seconds is not an int . ...然后switch块不会进入int case ,而是 go 进入default case ,因为Seconds不是int Developers might expect that case int: matches the type Seconds as well.开发人员可能期望case int:也匹配Seconds类型。

To allow a case statement to match both Seconds and int would require a new syntax, like, for example,要允许case语句同时匹配Secondsint将需要新的语法,例如,

case ~int:

As of this writing, the discussion is still open, and maybe it will result in an entirely new option for switching on a type parameter (such as, switch type T ).在撰写本文时,讨论仍处于开放状态,也许它会带来一个全新的选项来打开类型参数(例如, switch type T )。

More details, please refer to proposal: spec: generics: type switch on parametric types更多细节请参考提案:spec: generics: type switch on parametric types


Trick: convert the type into 'any'技巧:将类型转换为 'any'

Luckily, we do not need to wait for this proposal to get implemented in a future release.幸运的是,我们不需要等待该提案在未来的版本中实施。 There is a super simple workaround available right now.现在有一个超级简单的解决方法。

Instead of switching on v.(type) , switch on any(v).(type) .不要打开v.(type) ,而是打开any(v).(type)

switch any(v).(type) {
    ...

This trick converts v into an empty interface{} (aka any ), for which the switch happily does the type matching.这个技巧将v转换为一个空的interface{} (又名any ), switch很乐意为此进行类型匹配。


Source: A tip and a trick when working with generics资料来源: 使用 generics 时的提示和技巧

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C++ switch 语句,case 值为 reinterpret_cast(string) - C++ switch statement with case values of reinterpret_cast(string) 如何在Kotlin中获取泛型参数class - How to get class of generic type parameter in Kotlin 如何指定 var 中间语句的类型 - How to specify the type of a var mid-statement 如何键入 createAsyncThunk 的第二个泛型以接收零参数 - How to type the second generic of createAsyncThunk to receive zero parameters 如何在 Go 中创建泛型方法? (方法必须没有类型参数) - How to create generic method in Go? (method must have no type parameters) 有没有一种方法可以在没有反射的情况下在通用 function 中进行类型检查? - Is there a way to type check within a generic function without reflection? 如何使用类型开关来确定 protoreflect.MessageDescriptor 的类型? - How do I use a type switch to determine the type of a protoreflect.MessageDescriptor? 节点 TS,Google Cloud Run 的构建生成失败,给出错误类型“IsTuple”不是通用的 - Node TS, Build Generation Failed for Google Cloud Run, Giving Error Type 'IsTuple' not generic Flutter - 这个 function 的返回类型为 'FutureOr<xxx> ',但不以返回语句结束</xxx> - Flutter - This function has a return type of 'FutureOr<XXX>', but doesn't end with a return statement C# CosmosDb 查询错误 CS0266:无法隐式转换类型 'System.Linq.IQueryable <system.collections.generic.list<xxxx> ' 到</system.collections.generic.list<xxxx> - C# CosmosDb Query error CS0266: Cannot implicitly convert type 'System.Linq.IQueryable<System.Collections.Generic.List<xxxx>' to
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM