No existing question has an answer to this question.
In c# 7, can I switch directly on a System.Type
?
When I try:
switch (Type)
Adding to the above answer, if you do not need the value to be used inside the case statement, you can use _. This syntax can be used to remove an unused variable warning message.
switch (this.value) {
case int _:
//Do something else without value.
break;
case decimal decimalValue:
this.value = Math.Max(Math.Min(decimalValue, Maximum), Minimum);
break;
}
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 have 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;
}
...but it's important to note 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 { }
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)
{
// NOT EXECUTED <--- !
}
Since TDerived3
"is-a" TDerived2
, both cases are handled by the earlier condition when using is
matching. This highlights the different runtime semantics between 'strict' or 'exact' type equality versus a more nuanced notion of 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):
// NOT EXECUTED <-- !
break;
}
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).
Well, it probably break your code convention, but I use this approach;
var result = this.Value is int intValue
? Math.Max(Math.Min(intValue, Maximum), Minimum)
: this.Value is decimal decimalValue
? Math.Max(Math.Min(decimalValue, Maximum), Minimum)
: this.Value is double doubleValue
? Math.Max(Math.Min(decimalValue, Maximum), Minimum)
: throw new Exception($"Cannot handle '{this.Value.GetType().Name}' value.");
You can use Type.IsAssignableFrom()
in condition statement when it is more appropriate.
Here is an alternative which won't compile as the classes are missing:
bool? runOnUI = queuedAction.RunOnUI; // N=null, T=true F=False
bool isOnUI = Statics.CoreDispatcher.HasThreadAccess;
bool isFF = queuedAction.IsFireAndForget; // just makes it easier to read the switch statement
if (false == queuedAction.IsFireAndForget)
IsOtherTaskRunning = true;
/* In the case statements below the string name is something like noFF_TN
* The compiler ignores the string name. I have used them here to represent
* the logic in the case statement: ( FF = FireAnd Forget, T=true, F=false, N = null, * means 'whatever')
*
* isFF_** = FireAndForget QueuedAction
* noFF_** = Not FireAndForget so Wait for Task to Finish (as opposed to Complete)
* ****_T* = We are already on the UI thread
* ****_F* = We are not on the UI thread
* ****_*T = Run on the UI thread.
* ****_*F = Do not run on UI thread
* ****_*N = Don't care so run on current thread
*
* The last character is a "bool?" representing RunOnUI. It has
* three values each of which has a different meaning */
bool isTask;
switch ("ignore")
{
/* Run it as an Action (not Task) on current Thread */
case string noFF_TT when !isFF && isOnUI && runOnUI == true:
case string isFF_TN when isFF && isOnUI && !runOnUI == null:
case string isFF_FN when isFF && !isOnUI && runOnUI == null:
case string isFF_TT when isFF && isOnUI && runOnUI == true:
case string isFF_FF when isFF && !isOnUI && runOnUI == false:
isTask = false;
queuedAction.ActionQA(queuedAction); // run as an action, not as a Task
break;
/* Create a Task, Start it */
case string isFF_TF when isFF && isOnUI == true && runOnUI == false:
case string noFF_TN when !isFF && isOnUI == true && runOnUI == null: // <== not sure if I should run it on UI instead
case string noFF_TF when !isFF && isOnUI && runOnUI == false:
case string noFF_FN when !isFF && !isOnUI && runOnUI == null:
case string noFF_FF when !isFF && !isOnUI && runOnUI == false:
var cancellationTokenSource = new CancellationTokenSource();
queuedAction.Canceller?.SetCancellationTokenSource(cancellationTokenSource);
isTask = true;
new Task
(
(action) => queuedAction.ActionQA(queuedAction),
queuedAction,
cancellationTokenSource.Token
)
.Start();
break;
/* Run on UI and don't wait */
case string isFF_FT when isFF && !isOnUI && runOnUI == true:
isTask = true;
Statics.RunOnUI(() => queuedAction.ActionQA(queuedAction), asTaskAlways: true);
break;
/* Run on the UI as a Task and Wait */
case string noFF_FT when !isFF && !isOnUI && runOnUI == true:
isTask = true;
Statics.RunOnUI(() => queuedAction.ActionQA(queuedAction), asTaskAlways: true);
break;
default:
throw new LogicException("unknown case");
}
Starting with Paulustrious's idea of switching on a constant, but striving for the most readability:
Type type = GetMyType();
switch (true)
{
case bool _ when type == typeof(int):
break;
case bool _ when type == typeof(double):
break;
case bool _ when type == typeof(string):
break;
default:
break;
}
What's readable is subjective. I used to do something similar in VB a long time ago so I got used to this form (but in VB the bool _
was not needed so it wasn't there). Unfortunately in c# the bool _
required. I'm using c# 7.0 and I think switching on a constant may not be supported in earlier compilers but I am not sure about that, so try it if you want to. I think it's kindof amusing that the S/O code formatter doesn't know about when
yet.
You wouldn't want to do this of course if you need the case
variable, like for subclasses.
But for arbitrary boolean expressions it is more suited, for example:
switch (true)
{
case bool _ when extruder.Temperature < 200:
HeatUpExtruder();
break;
case bool _ when bed.Temperature < 60:
HeatUpBed();
break;
case bool _ when bed.Y < 0 || bed.Y > 300:
HomeYAxis();
break;
default:
StartPrintJob();
break;
}
Some will argue this is worse than if..else. The only thing I can say is switch
forces one path and it's impossible to break the switch
statement itself, but it is possible to leave out an else
and break an if..else into multiple statements unintentionally, possibly executing two "branches" accidentally.
Switching on a Type
is really just an arbitrary switch because what we are really switching on is a property of the variable. Unless and until we can do case typeof(int)
(case
on something that is not a constant expression), we are stuck with something akin to this if we don't want to use string constants, which in the case of type names, are not in the framework.
I know it's not applicable in all cases, but maybe my example will help someone. In ASP.NET Core I implemented my custom model binder provider and I had to resolve binder type depending on model type.
The initial idea (apart from the if/else block ofc but I kept thinking it could be shorter) was the switch:
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
switch (context.Metadata.ModelType)
{
case Type _ when context.Metadata.ModelType == typeof(Model1):
return new BinderTypeModelBinder(typeof(Binder1));
case Type _ when context.Metadata.ModelType == typeof(Model2):
return new BinderTypeModelBinder(typeof(Binder2));
case Type _ when context.Metadata.ModelType == typeof(Model3):
return new BinderTypeModelBinder(typeof(Binder3));
}
return null;
}
This is the same with a dictionary:
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
var bindersByType = new Dictionary<Type, Type>
{
{typeof(Model1), typeof(Binder1)},
{typeof(Model2), typeof(Binder2)},
{typeof(Model3), typeof(Binder3)}
};
return bindersByType.TryGetValue(context.Metadata.ModelType, out Type binderType) ? new BinderTypeModelBinder(binderType) : null;
}
Credits for the idea go to @xxbbcc who posted this in a comment to the first question