Switching on a Generic Type

Switch case and generics checking

You can use the TypeCode enum for switch:

switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Int32:
...
break;
case TypeCode.Decimal:
...
break;
}

Since C# 7.0 you can use pattern matching:

switch (obj)
{
case int i:
...
break;
case decimal d:
...
break;
case UserDefinedType u:
...
break;
}

Beginning with C# 8.0 you can use switch expressions:

string result = obj switch {
int i => $"Integer {i}",
decimal d => $"Decimal {d}",
UserDefinedType u => "User defined {u}",
_ => "unexpected type"
};

Generic type in a switch statement

*A[any] does not match *A[int] because any is a static type, not a wildcard. 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. T won't be inferred from the struct alone.

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

Playground: 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.

In order to improve the code here you can make Handle a top-level 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. For this, you might:

  • simply pass an additional *db argument to Handle and handleTransactionalCommand, which also helps with type parameter inference. Call as Handle(ctx, &db{}, tFn). Playground: https://go.dev/play/p/6WESb86KN5D

  • pass an instance of CommandManager (like solution above but *db is wrapped). Much more verbose, as it requires explicit instantiation everywhere. Playground: 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

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

Switching on type with a generic return type

If I understand your intention correctly - you can do it like this:

// no need to pass instance of T - why?
public Mock<DbSet<T>> Mocked<T>() where T : class
{
if (typeof(T) == typeof(Workflow)) {
// first cast to object, then to return type to avoid compile error
// compiler does not know mockedWorkFlows is Mock<DbSet<T>>, but you
// know it already, because you checked type 'T'
return (Mock<DbSet<T>>) (object) mockedWorkFlows; //cannot Workflow to T
}
// etc
return null;
}

Whether it is good idea or not is a different story.

Switching on a generic type?

You need the is pattern:

func doSomething<T>(type: T.Type) {
switch type {
case is String.Type:
print("It's a String")
case is Int.Type:
print("It's an Int")
default:
print("Wot?")
}
}

Note that the break statements are usually not needed, there is no
"default fallthrough" in Swift cases.

How to use Switch...Case in generic type parameter in C#?

If you need to know what the type of T is, you probably aren't using generics properly. Ask yourself: what happens if someone uses a T that you haven't accounted for?

It seems like what you need is to have a base class Response and then derive the other classes from it, then you can create a factory method which produces the appropriate instances of the derived classes depending on some logic.



Related Topics



Leave a reply



Submit