Reputation: 3502
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
Reputation: 18749
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
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
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