GThree
GThree

Reputation: 3502

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

I have a class where I am using Generic Type Parameter to dynamically use it. Right now, I am using if..else which works fine with my custom classes. I was wondering if I can use switch..case here.

If data types are decimal or int then I can use TypeCode like this.

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

But I have custom classes which I created and I want to use them. Below code works and trying to use switch..case if possible.

My Code:

void LogError<T>(T response, string orderId)
{
    if (typeof(T) == typeof(QuoteResponse))
    {
        var quoteResponse = response as QuoteResponse;

        //Do something
    }
    else if (typeof(T) == typeof(CommitResponse))
    {
        var commitResponse = response as CommitResponse;

        //Do something
    }
    else if (typeof(T) == typeof(CancelResponse))
    {
        var cancelResponse = response as CancelResponse;

        //Do something
    }
    else if (typeof(T) == typeof(StatusResponse))
    {
        var statusResponse = response as StatusResponse;

        //Do something
    }
}

Upvotes: 3

Views: 7596

Answers (3)

Glenn Slayden
Glenn Slayden

Reputation: 18749

C# switch statement on a generic type argument, i.e. <T>


0. Demonstration types used in the code below.

class MyClass { };    struct MyStruct { };    enum MyEnum { };

1. Globally in your project, define a struct with a single generic type argument as follows.

public struct TokenOf<X> { };

3. switch statement. This demo shows a generic method, but of course the technique can also be used anywhere within the scope of a generic class or struct: (Note that this method uses switch expression syntax available in C# 8):

string GenericMethod<T>() =>
    default(TokenOf<T>) switch
    {
        TokenOf<int> _      /**/ => "II",
        TokenOf<Guid> _     /**/ => "GG",
        TokenOf<string> _   /**/ => "SS",
        TokenOf<object> _   /**/ => "OO",
        TokenOf<MyEnum> _   /**/ => "EE",
        TokenOf<MyClass> _  /**/ => "CC",
        TokenOf<MyStruct> _ /**/ => "QQ",
        _                   /**/ => "??",
    };

3a. In these switch expression examples, you can use { } instead of the "discard symbol" _ for equivalent results. This suggests cute (or perhaps abusive) code formatting options such as:

string GenericMethod<T>() => default(TokenOf<T>) switch
{
    TokenOf<int>      /**/ _ => "II",
    TokenOf<Guid>     /**/ _ => "GG",
    // etc...
    TokenOf<MyStruct> /**/ _ => "QQ",
    {                 /**/ } => "??",
};

4. Demonstration test code

void switch_on_generic_type() =>
    Console.WriteLine($@"{
        GenericMethod<int>()}   {
        GenericMethod<Guid>()}   {
        GenericMethod<string>()}   {
        GenericMethod<object>()}   {
        GenericMethod<MyEnum>()}   {
        GenericMethod<MyClass>()}   {
        GenericMethod<MyStruct>()}   {
        GenericMethod<double>()}");
    }

5. Output of the demo

II GG SS OO EE CC QQ ??


6. Discussion

This technique takes advantage of the fact that a .NET value-type can never not-be instantiated. It follows that value-types (unlike as with reference types--which for example lose their System.Type identity if null is stored in an underspecified variable) can never lose their specific identity. Therefore, the TokenOf<> struct is guaranteed to represent a different System.Type identity for each different parameterization by X, even when all are always "default" instances (which are actually 1-byte in size). The generic type argument of the struct is necessary and sufficient to differentiate them.

To be clear, note that while TokenOf<> therefore must be a struct, the switched-upon generic type argument <X> can be any valid .NET type. And in fact, if X is guaranteed to represent a value-type, you don't even need the TokenOf<> wrapper at all. For example, consider a generic class or method with a type argument T constrained by the unmanaged (or struct) keyword. In this case, an example such as (3.) from above would reduce to this:

string GenericMethod<T>() where T : unmanaged =>
    default(T) switch
    {
        int _      /**/ => "II",
        Guid _     /**/ => "GG",
        MyEnum _   /**/ => "EE",
        MyStruct _ /**/ => "QQ",
        TimeSpan _ /**/ => "TS",
        _          /**/ => "??",
    };

The typeof(T) operator is used in other answers on this page, but because it (by definition) manifests a reification operation on T that's both inherently opaque to static analysis, and existentially hard-blocked on a System.Type instance (which itself, obviously, can only exist at runtime) I consider it to be a pretty blunt and dramatic tool.

By avoiding typeof, the technique shown here may be friendlier to compilers, optimizers, analyzers, and other CTS offline-metadata systems (pending investigation). Most-importantly, a CIL regime which is relaxed of excessive forcible System.Type instantiations (via typeof) may prove favorable to the JIT.

Upvotes: 7

user9539019
user9539019

Reputation:

This is actually a bad design, because generic methods are supposed to be generic.

In case you really need it though, you can use pattern matching to make the code concise --

switch (response)
{
    case QuoteResponse q:
        // do something
    case CommitResponse ct:
        // do something else
    ...
}

Upvotes: 0

rory.ap
rory.ap

Reputation: 35270

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.

Upvotes: 3

Related Questions