Problem
Is there a better method to imitate switching on type than this, given that C# can’t switch on a Type (which I gather wasn’t introduced as a special case because its relationships mean that more than one different example might apply)?
void Foo(object o)
{
if (o is A)
{
((A)o).Hop();
}
else if (o is B)
{
((B)o).Skip();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
Asked by xyz
Solution #1
With C# 7, which shipped with Visual Studio 2017 (Release 15.*), you are able to use Types in case statements (pattern matching):
switch(shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("<unknown shape>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
Thanks to @Joey Adams, you can use a switch statement with the nameof() operator in C# 6:
switch(o.GetType().Name) {
case nameof(AType):
break;
case nameof(BType):
break;
}
You could use a switch statement in C# 5 and earlier, but you’d have to use a magic string containing the type name, which isn’t really refactor friendly (thanks @nukefusion).
switch(o.GetType().Name) {
case "AType":
break;
}
Answered by Zachary Yates
Solution #2
Switching on types is not supported in C# (UPDATE: switching on types is supported in C#7 / VS 2017 – read Zachary Yates’ explanation). You’ll need to use a different structure if you don’t want to use a large if/else if/else statement. I posted a blog post about how to create a TypeSwitch structure a while back.
Abridged version: TypeSwitch was created to eliminate superfluous casting and provide a syntax that is similar to that of a standard switch/case statement. Here’s an example of TypeSwitch in action on a typical Windows form event.
TypeSwitch.Do(
sender,
TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));
TypeSwitch’s code is actually quite small and may be simply included into your project.
static class TypeSwitch {
public class CaseInfo {
public bool IsDefault { get; set; }
public Type Target { get; set; }
public Action<object> Action { get; set; }
}
public static void Do(object source, params CaseInfo[] cases) {
var type = source.GetType();
foreach (var entry in cases) {
if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
entry.Action(source);
break;
}
}
}
public static CaseInfo Case<T>(Action action) {
return new CaseInfo() {
Action = x => action(),
Target = typeof(T)
};
}
public static CaseInfo Case<T>(Action<T> action) {
return new CaseInfo() {
Action = (x) => action((T)x),
Target = typeof(T)
};
}
public static CaseInfo Default(Action action) {
return new CaseInfo() {
Action = x => action(),
IsDefault = true
};
}
}
Answered by JaredPar
Solution #3
A dictionary from Type to Action is one option (or some other delegate). Then, based on the kind, look up and do the action. I’ve previously used this for factories.
Answered by Jon Skeet
Solution #4
I built a variation of JaredPar’s TypeSwitch class that employs type inference for a cleaner syntax with JaredPar’s answer in mind:
class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }
public string GetName(object value)
{
string name = null;
TypeSwitch.On(value)
.Case((C x) => name = x.FullName)
.Case((B x) => name = x.LongName)
.Case((A x) => name = x.Name)
.Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
.Case((Y x) => name = x.GetIdentifier())
.Default((x) => name = x.ToString());
return name;
}
It’s worth noting that the sequence in which the Case() methods are called is crucial.
Get the complete code for my TypeSwitch class, along with comments. This is a shortened version for working purposes:
public static class TypeSwitch
{
public static Switch<TSource> On<TSource>(TSource value)
{
return new Switch<TSource>(value);
}
public sealed class Switch<TSource>
{
private readonly TSource value;
private bool handled = false;
internal Switch(TSource value)
{
this.value = value;
}
public Switch<TSource> Case<TTarget>(Action<TTarget> action)
where TTarget : TSource
{
if (!this.handled && this.value is TTarget)
{
action((TTarget) this.value);
this.handled = true;
}
return this;
}
public void Default(Action<TSource> action)
{
if (!this.handled)
action(this.value);
}
}
}
Answered by Daniel A.A. Pelsmaeker
Solution #5
Pattern matching is available in C# 7 and higher:
switch (foo.GetType())
{
case var type when type == typeof(Player):
break;
case var type when type == typeof(Address):
break;
case var type when type == typeof(Department):
break;
case var type when type == typeof(ContactType):
break;
default:
break;
}
Answered by alhpe
Post is based on https://stackoverflow.com/questions/298976/is-there-a-better-alternative-than-this-to-switch-on-type