Is There a Better Alternative Than This to 'Switch on Type'

Is there a better alternative than this to 'switch on type'?

Switching on types is definitely lacking in C# (UPDATE: in C#7 / VS 2017 switching on types is supported - see Zachary Yates's answer). In order to do this without a large if/else if/else statement, you'll need to work with a different structure. I wrote a blog post awhile back detailing how to build a TypeSwitch structure.

https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types

Short version: TypeSwitch is designed to prevent redundant casting and give a syntax that is similar to a normal switch/case statement. For example, here is TypeSwitch in action on a standard 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"));

The code for TypeSwitch is actually pretty small and can easily be put 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
};
}
}

Is there a better alternative than this to 'switch on type'?

Switching on types is definitely lacking in C# (UPDATE: in C#7 / VS 2017 switching on types is supported - see Zachary Yates's answer). In order to do this without a large if/else if/else statement, you'll need to work with a different structure. I wrote a blog post awhile back detailing how to build a TypeSwitch structure.

https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types

Short version: TypeSwitch is designed to prevent redundant casting and give a syntax that is similar to a normal switch/case statement. For example, here is TypeSwitch in action on a standard 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"));

The code for TypeSwitch is actually pretty small and can easily be put 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
};
}
}

looking alternative way for this switch case or any other solution

Instead of passing in the parameter MasterSectionEnum, change your method to accept a type parameter. Then just use that generic type in a single call of UpdateRevision.

public async Task<MutationResponse> SetRequestStage<SectionType>(
string requestStage,
Guid requestId
) {

...
await UpdateRevision<SectionType>(request.DataId).ConfigureAwait(false);
....

}

If all valid types of SectionType share an interface or are derived from the same class, then you can take it further and add a constraint to the SectionType and avoid having to handle bad types at runtime.




Edit:

So SetRequestStage can't be generic huh? Well, the inclination to make it generic stems from the fact that it depends on UpdateRevision, which is generic. This in turn depends on DbContext.Set(). And you're using a generic version of it. But the good news is that there seems to be a non-generic version of it that accepts a type variable as a parameter. So:

async Task UpdateRevision<T>(
Guid id,
Type t
) where T : class, IAEIMaster, IRevisionData {

var dbSet = this._dbContext.Set(t);
...

}

And then:

public async Task<MutationResponse> SetRequestStage(
string requestStage,
Guid requestId,
Type SectionType
) {

...
await UpdateRevision(request.DataId, SectionType).ConfigureAwait(false);
....

}

I don't know what your UI looks like. But, generically:

var dic = new Dictionary<MasterSectionEnum, Type> {
{ MasterSectionEnum.LOCALCODE, typeof(LocalCode) },
{ MasterSectionEnum.NATIONALCODE, typeof(NationalCode) },
...
};

public async someUiRelatedMethod(
string reqStage,
Guid reqId,
MasterSectionEnum sectionType
) {

await SetRequestStage(reqStage, reqId, dic[sectionType]);

}

Sorry if the syntax on the latter isn't quite right. But you get the idea.

Alternative to the switch Statement

I have only a note about your second approach, you shouldn't use an Array to store non-numeric indexes (that you would call in other languages an associative array).

You should use a simple Object.

Also, you might want to check if the what argument passed to your write function exists as a property of your colors object and see if it's a function, so you can invoke it without having run-time errors:

var colors = {};

colors['Blue'] = function() { alert('Blue'); };
colors['Red'] = function() { alert('Red'); };


function write(what) {
if (typeof colors[what] == 'function') {
colors[what]();
return;
}
// not a function, default case
// ...
}

Is there an alternative to using lots of switch statements in a class?

The OOP correctly implemented is the solution for this problem. It is called subtyping and it's one kind of polymorphism.

You crammed the functionality of all your page types in a single class and that's why you ended up with lots of switch statements. Try to identify the differences between your page types and create one empty generic function for each functionality. Then extend the current page class and implement in each child class the functionality specific to one type of page.

Something like this:

abstract class Page
{
abstract public function loadTable();
abstract protected function getSearchQuery($term);

public function searchRecord($term);
{
$query = $this->getSearchQuery($term);
// rest of search code
}
}

class GreenPage extends Page
{
public function loadTable()
{
// move the code of function Page::loadGreen() here
}

protected function getSearchQuery($term)
{
return "SELECT `green_ID`,Reason FROM `green`";
}
}

class BluePage extends Page
{
public function loadTable()
{
// move the code of function Page::loadBlue() here
}

protected function getSearchQuery($term)
{
return "SELECT `blue_ID`,Reason FROM `blue`";
}
}

Then you probably need to create a Factory object; based on some characteristics you pass it, it knows how to create the correct type of Page object:

class PageFactory
{
public function createPage($name)
{
switch ($name)
{
case 'green':
return new GreenPage();
case 'blue':
return new BluePage();
case 'red':
return new RedPage();
default:
// this either should never happen or you have a type of page as default
}
}
}


$factory = new PageFactory();
$page = $factory->createPage($name);
$page->loadTable();
$page->searchRecord(trim($_POST['term']));

Why is this switch on type case considered confusing?

It seems you don't expect the switch to match on subclasses. But this would break the Liskov Substitution Principle. (where if you passed in a C object, the code would work, but not with a D, even though D is a subclass of C).



Related Topics



Leave a reply



Submit