The issue raised here by the OP is that you can't use the new C# 7 type-based switch feature when you don't have an actual instance of the switched-upon type available, and you instead have only its putative System.Type
. The accepted answer, summarized as follows, works well for exact type matching (minor improvement shown here, but see my final example below for yet further streamlining)...
Type type = ...
switch (type)
{
case Type _ when type == typeof(Int32):
case Type _ when type == typeof(Decimal):
this.value = Math.Max(Math.Min(this.value, Maximum), Minimum);
break;
}
The important point to note is that for derived reference type hierarchies, this will not exhibit the same behavior as an if... else
chain which uses the is
keyword for matching. Consider:
class TBase { }
class TDerived1 : TBase { }
class TDerived2 : TBase { }
sealed class TDerived3 : TDerived2 { }
TBase inst = ...
if (inst is TDerived1)
{
// Handles case TDerived1
}
else if (inst is TDerived2)
{
// Handles cases TDerived2 and TDerived3
}
else if (inst is TDerived3)
{
// IMPOSSIBLE (never executed) <--- !
}
Since TDerived3
"is-a" TDerived2
, when using is
matching, both cases are handled by the second branch. (And since TDerived3
is marked "sealed" in this example, no instance can ever match the third branch.)
This highlights the difference at runtime between 'strict' or 'exact' type equality versus the more nuanced notion of type subsumption (type "compatibility"). Because the types in the OP's question were ValueType
primitives (which can't be derived-from), the difference couldn't matter. But if we adapt the 'exact type matching' of the accepted answer with the example classes shown above, we will get a different result:
Type type = ...
switch (type)
{
case Type _ when type == typeof(TDerived1):
// Handles case TDerived1
break;
case Type _ when type == typeof(TDerived2):
// Handles case TDerived2
break;
case Type _ when type == typeof(TDerived3):
// Handles case TDerived3 <--- !
break;
}
In fact, C# 7 won't even compile a switch
statement which corresponds to the if / else
sequence shown earlier. (n.b. It seems like the compiler should detect this as a warning, rather than an error, since the harmless result is just a branch of inaccessible code--a condition which the compiler deems a warning elsewhere--and also considering that the compiler doesn't even detect, at all, the seemingly identical situation in the if / else
version). Here's that:

In any case, which one of the alternate behaviors is appropriate, or if it even matters, will depend on your application, so my point here is just to draw attention to the distinction. If you determine that you need the more savvy type-compatibility version of the switch approach, here is how to do it:
Type type = ...
switch (type)
{
case Type _ when typeof(TDerived1).IsAssignableFrom(type):
// Handles case TDerived1
break;
case Type _ when typeof(TDerived2).IsAssignableFrom(type):
// Handles cases TDerived2 and TDerived3
break;
case Type _ when typeof(TDerived3).IsAssignableFrom(type):
// IMPOSSIBLE (never executed) <--- !
break;
}
As before, the third case is extraneous, since TDerived3
is sealed for the current example.
Finally, as I mentioned in another answer on this page, you can simplify this usage of the switch
statement even further. Since we're only using the when
clause functionality, and since we presumably still have the original switched-upon Type
instance available in a variable, there's no need to mention that variable in the switch
statement, nor repeat its Type (Type
, in this case) in each case
. Just do the following instead:
Type type = ...
switch (true)
{
case true when typeof(TDerived1).IsAssignableFrom(type):
break;
case true when typeof(TDerived2).IsAssignableFrom(type):
break;
case true when typeof(TDerived3).IsAssignableFrom(type):
break;
}
Notice the switch(true)
and case(true)
. I recommend this simpler technique whenever you are relying only on the when
clause (that is, beyond just the situation of switching on System.Type
as discussed here).