Parsing CSV data using a finite state machine - c#

I want to read a file containing comma-separated values, so have written a finite state machine:
private IList<string> Split(string line)
{
List<string> values = new List<string>();
string value = string.Empty;
ParseState state = ParseState.Initial;
foreach (char c in line)
{
switch (state)
{
case ParseState.Initial:
switch (c)
{
case COMMA:
values.Add(string.Empty);
break;
case QUOTE:
state = ParseState.Quote;
break;
default:
value += c;
state = ParseState.Data;
break;
}
break;
case ParseState.Data:
switch (c)
{
case COMMA:
values.Add(value);
value = string.Empty;
state = ParseState.Initial;
break;
case QUOTE:
throw new InvalidDataException("Improper quotes");
default:
value += c;
break;
}
break;
case ParseState.Quote:
switch (c)
{
case QUOTE:
state = ParseState.QuoteInQuote;
break;
default:
value += c;
break;
}
break;
case ParseState.QuoteInQuote:
switch (c)
{
case COMMA:
values.Add(value);
value = string.Empty;
state = ParseState.Initial;
break;
case QUOTE:
value += c;
state = ParseState.Quote;
break;
default:
throw new InvalidDataException("Unpaired quotes");
}
break;
}
}
switch (state)
{
case ParseState.Initial:
case ParseState.Data:
case ParseState.QuoteInQuote:
values.Add(value);
break;
case ParseState.Quote:
throw new InvalidDataException("Unclosed quotes");
}
return values;
}
Yes, I know the advice about CSV parsers is "don't write your own", but
I needed it quickly and
our download policy at work would take several days to allow me to
get open source off the 'net.
Hey, at least I didn't start with string.Split() or, worse, try using a Regex!
And yes, I know it could be improved by using a StringBuilder, and it's restrictive on quotes in the data, but
performance is not an issue and
this is only to generate well-defined test data in-house,
so I don't care about those.
What I do care about is the apparent trailing block at the end for mopping up all the data after the final comma, and the way that it's starting to look like some sort of an anti-pattern down there, which was exactly the sort of thing that "good" patterns such as a FSM were supposed to avoid.
So my question is this: is this block at the end some sort of anti-pattern, and is it something that's going to come back to bite me in the future?

All of the FSMs I've ever seen (not that I go hunting for them, mind you) all have some kind of "mopping up" step, simply due to the nature of enumeration.
In an FSM you're always acting upon the current state, and then resetting the 'current state' for the next iteration, so once you've hit the end of your iterations you have to do one last operation to act upon the 'current state'. (Might be better to think about it as acting upon the 'previous state' and setting the 'current state').
Therefore, I would consider that what you've done is part of the pattern.
But why didn't you try some of the other answers on SO?
Split CSV String (specifically this answer)
How to properly split a CSV using C# split() function? (specifically this answer)
Adapted solution, still an FSM:
public IEnumerable<string> fsm(string s)
{
int i, a = 0, l = s.Length;
var q = true;
for (i = 0; i < l; i++)
{
switch (s[i])
{
case ',':
if (q)
{
yield return s.Substring(a, i - a).Trim();
a = i + 1;
}
break;
// pick your flavor
case '"':
//case '\'':
q = !q;
break;
}
}
yield return s.Substring(a).Trim();
}
// === usage ===
var row = fsm(csvLine).ToList();
foreach(var column in fsm(csvLine)) { ... }

In a FSM you identify which states are the permitted halting states. So in a typical implementation, when you come out of the loop you need to at least check to make sure that your last state is one of the permitting halting states or throw a jam error. So having that one last state check outside of the loop is part of the pattern.

The source of the problem, if you want to call it that, is the absence of an end-of-line marker in your input data. Add a newline character, for example, at the end of your input string and you will be able to get rid of the "trailing block" that seems to annoy you so much.
As far as I'm concerned, your code is correct and, no, there is no reason why this implementation will come back to bite you in the future!

I had a similiar issue, but i was parsing a text file character by character. I didnt like this big clean-up-switch-block after the while loop. To solve this, I made a wrapper for the streamreader. The wrapper checked when streamreader had no characters left. In this case, the wrapper would return an EOT-ascii character once (EOT is equal to EOF). This way my state machine could react to the EOF depending on the state it was in at that moment.

Related

Is it possible to use the .ToUpper() method in C# on a string value that isn't stored in a variable?

I have a program that reads in parameters from a file. The file is read in one line at a time, and each line is checked to see if it holds a specific value or a blank. If line isn't blank space, the value in the line is passed to a switch statement. Here is a part of the swtich statement in question:
switch(stName)
{
//GENERAL section
case "JOBNAME":
_JobName = stValue;
break;
case "RUN AS-OF DATE":
_RunDate = stValue;
break;
case "USER NOTIFICATION EMAIL ADDRESS":
_UserEmailAddr = stValue;
break;
default:
System.Exception ex = new System.Exception("Unexpected parameter");
ex.Data.Add("Config File", oCommandArgs.ConfigFile);
ex.Data.Add("Parm Line", stIniLine);
ex.Data.Add("Delimiter", cDelimiter);
ex.Data.Add("Name", stName);
ex.Data.Add("Value", stValue);
throw ex;
}
The value in stName is converted to upper case prior to going to the switch statement. My question is, is it possible to use the .ToUpper() method on a string value that isn't stored in a variable? Basically, so that the code would resemble something like this:
switch(stName)
{
//GENERAL section
case "Jobname".ToUpper():
_JobName = stValue;
break;
case "Run as-of date".ToUpper():
_RunDate = stValue;
break;
case "User notification e-mail address".ToUpper():
_UserEmailAddr = stValue;
break;
default:
System.Exception ex = new System.Exception("Unexpected parameter");
ex.Data.Add("Config File", oCommandArgs.ConfigFile);
ex.Data.Add("Parm Line", stIniLine);
ex.Data.Add("Delimiter", cDelimiter);
ex.Data.Add("Name", stName);
ex.Data.Add("Value", stValue);
throw ex;
}
This is just to help simplify adding additional parameters.
In a switch statement, the case labels must be compile time constants; the compiler must know the result of the expression at compile time. The result of ToUpper() can not be, in C#, a compile time constant, code has to run in order to know the result, and therefore can't be used as you intend to.
const int one = 1;
const char c = 'c';
case 'c':
case c:
case 1:
case one:
case one + 1:
case default(int):
Are all valid.
int two = 2;
char a = 'a';
case two: //two is a variable
case a: //a is a variable
case 'c'.ToUpper(); //result of ToUpper is not known at compile time
case one.CompareTo(one): //result of CompareTo is not known at compile time
Are not.
Most practical solution? Decide on a casing criteria for your labels, write them all uppercase, or lower case, just be consistent, and then simply format accordingly the variable you are switching on:
switch(stName.ToUpper()) //<<<normalize casing here
{
case "JOBNAME":
_JobName = stValue;
break;
case "RUN AS-OF DATE":
_RunDate = stValue;
break;
...
}
If you have have to deal with localized upper and lower casing then use ToUpperInvariant or ToLowerInvariant. If that is not an option then switch is probably not the right tool for the job and you'll want to solve this with regular if-elseif-else statements.
Yes it is 100% possible and valid to do that on a string. You have access to all string methods doing it that way. However, switch statements require a constant value so you would not be able to do that in a switch. I would take a look at your code again and see why you would need it. Why not just write the value in all caps?
EDIT: Switch statements require a constant value so the way you are intending to use is NOT valid. I have edited my answer to reflect that.

Running non-static method with a string as parameters

I use s.Keyboard.Keypress(VirtualKeyCode.VK_H); method. And I want to be able to swap that VK_H value to other characters of "Test" we are running. I am using InputSimulation library.
So in the test it would run series of input like:
s.Keyboard.KeyPress(VirtualKeyCode.VK_T);
s.Keyboard.KeyPress(VirtualKeyCode.VK_E);
s.Keyboard.KeyPress(VirtualKeyCode.VK_S);
s.Keyboard.KeyPress(VirtualKeyCode.VK_T);
My code is
InputSimulator s = new InputSimulator();
char[] word = "Test".ToCharArray();
for(int i = 0; i<word.Length; i++)
{
s.Keyboard.KeyPress(VirtualKeyCode.VK_H);
}
I cannot think of a scenario where I (as a Reviewer) would let C# code pass that relies on the name of an enum record. The records in an enum should be considered as "Names for numbers" and simply help you as a programmer to recognize numbers.
Using something like VirtualKeyCode.VK_T.ToString() is often used but should be avoided in my opinion (Let's leave logging and debug purpose out of the way for now). I understand that it's a super simple way of getting a text out of that value, but it is ugly. Now to your question. I would use this:
InputSimulator s = new InputSimulator();
foreach(var c in "test")
{
VirtualKeyCode keyCode;
switch(c)
{
case 't':
keyCode = VirtualKeyCode.VK_T; //Used twice in the text "test"
return;
case 'e':
keyCode = VirtualKeyCode.VK_E;
return;
case 's':
keyCode = VirtualKeyCode.VK_S;
return;
default:
throw new NotImplementedException($"The following character has not been implemented correctly: {c}");
}
//Go ahead and use keyCode here
}

alternatives to switch-case

I want to know if this kind of switch-case usage is appropriate, or there is any other alternatives (patterns)?
the following is part of my program:
the basics is I am doing a sequence of actions
generally program control is following the sequence of case one by one;
usually any specific case is not finished in its first call, we have to wait until the procX returns true. (waiting for instrument response or action completion);
jump to a specific case is possible (changing StepCurrent in the sampling code).
I found this kind of switch-case is hard to maintain, especially by changing the StepCurrent to direct control flow. And code looks ugly.
is there any better method?
note: though I am using C#, the problem might not be limited to it.
while (true)
{
if (sig_IsExit())
{
break;
}
Thread.Sleep(500);
bRetSts = false;
switch (StepCurrent) // nSeq)
{
case 0:
bRetSts = proc0();
break;
case 1:
bRetSts = proc1();
break;
case 2:
bRetSts = proc2();
break;
case 3:
bRetSts = proc3();
break;
case 4:
...
}
if( bRetSts )
StepCurrent++;
}
You can use a Dictionary<int,Func<bool>> , with this you will have less cyclomatic complexity, see the example:
Note: i use Dictionary to show that you can use any type as key, for example a string with a name, or an enum.
Dictionary<int,Func<bool>> proc = new Dictionary<int,Func<bool>>
{
{0, proc0},
{1, proc1},
{2, proc2},
{3, proc3},
}
and than use like that:
while (true)
{
if (sig_IsExit())
break;
Thread.Sleep(500);
bRetSts = false;
bRetSts = proc[StepCurrent]();
if( bRetSts )
StepCurrent++;
}
bRetSts = (StepCurrent == 0)? proc0():
(StepCurrent == 1)? proc1():
(StepCurrent == 2)? proc2():
(StepCurrent == 3)? proc3():
false; // it could be more proper to throw an exception
or, perhaps more appropriate if all procX have the same signature:
var funcs = new Func<bool>[] { proc0, proc1, proc2, proc3 };
funcs[StepCurrent]();
I think this is perfect opportunity to use Chain of responsibility design pattern.
Here is one of the better descriptions I found: https://sourcemaking.com/design_patterns/chain_of_responsibility Also example of implementation: http://www.tutorialspoint.com/design_pattern/chain_of_responsibility_pattern.htm

How to make C# Switch Statement use IgnoreCase

If I have a switch-case statement where the object in the switch is string, is it possible to do an ignoreCase compare?
I have for instance:
string s = "house";
switch (s)
{
case "houSe": s = "window";
}
Will s get the value "window"? How do I override the switch-case statement so it will compare the strings using ignoreCase?
A simpler approach is just lowercasing your string before it goes into the switch statement, and have the cases lower.
Actually, upper is a bit better from a pure extreme nanosecond performance standpoint, but less natural to look at.
E.g.:
string s = "house";
switch (s.ToLower()) {
case "house":
s = "window";
break;
}
Sorry for this new post to an old question, but there is a new option for solving this problem using C# 7 (VS 2017).
C# 7 now offers "pattern matching", and it can be used to address this issue thusly:
string houseName = "house"; // value to be tested, ignoring case
string windowName; // switch block will set value here
switch (true)
{
case bool b when houseName.Equals("MyHouse", StringComparison.InvariantCultureIgnoreCase):
windowName = "MyWindow";
break;
case bool b when houseName.Equals("YourHouse", StringComparison.InvariantCultureIgnoreCase):
windowName = "YourWindow";
break;
case bool b when houseName.Equals("House", StringComparison.InvariantCultureIgnoreCase):
windowName = "Window";
break;
default:
windowName = null;
break;
}
This solution also deals with the issue mentioned in the answer by #Jeffrey L Whitledge that case-insensitive comparison of strings is not the same as comparing two lower-cased strings.
By the way, there was an interesting article in February 2017 in Visual Studio Magazine describing pattern matching and how it can be used in case blocks. Please have a look: Pattern Matching in C# 7.0 Case Blocks
EDIT
In light of #LewisM's answer, it's important to point out that the switch statement has some new, interesting behavior. That is that if your case statement contains a variable declaration, then the value specified in the switch part is copied into the variable declared in the case. In the following example, the value true is copied into the local variable b. Further to that, the variable b is unused, and exists only so that the when clause to the case statement can exist:
switch(true)
{
case bool b when houseName.Equals("X", StringComparison.InvariantCultureIgnoreCase):
windowName = "X-Window";):
break;
}
As #LewisM points out, this can be used to benefit - that benefit being that the thing being compared is actually in the switch statement, as it is with the classical use of the switch statement. Also, the temporary values declared in the case statement can prevent unwanted or inadvertent changes to the original value:
switch(houseName)
{
case string hn when hn.Equals("X", StringComparison.InvariantCultureIgnoreCase):
windowName = "X-Window";
break;
}
As you seem to be aware, lowercasing two strings and comparing them is not the same as doing an ignore-case comparison. There are lots of reasons for this. For example, the Unicode standard allows text with diacritics to be encoded multiple ways. Some characters includes both the base character and the diacritic in a single code point. These characters may also be represented as the base character followed by a combining diacritic character. These two representations are equal for all purposes, and the culture-aware string comparisons in the .NET Framework will correctly identify them as equal, with either the CurrentCulture or the InvariantCulture (with or without IgnoreCase). An ordinal comparison, on the other hand, will incorrectly regard them as unequal.
Unfortunately, switch doesn't do anything but an ordinal comparison. An ordinal comparison is fine for certain kinds of applications, like parsing an ASCII file with rigidly defined codes, but ordinal string comparison is wrong for most other uses.
What I have done in the past to get the correct behavior is just mock up my own switch statement. There are lots of ways to do this. One way would be to create a List<T> of pairs of case strings and delegates. The list can be searched using the proper string comparison. When the match is found then the associated delegate may be invoked.
Another option is to do the obvious chain of if statements. This usually turns out to be not as bad as it sounds, since the structure is very regular.
The great thing about this is that there isn't really any performance penalty in mocking up your own switch functionality when comparing against strings. The system isn't going to make a O(1) jump table the way it can with integers, so it's going to be comparing each string one at a time anyway.
If there are many cases to be compared, and performance is an issue, then the List<T> option described above could be replaced with a sorted dictionary or hash table. Then the performance may potentially match or exceed the switch statement option.
Here is an example of the list of delegates:
delegate void CustomSwitchDestination();
List<KeyValuePair<string, CustomSwitchDestination>> customSwitchList;
CustomSwitchDestination defaultSwitchDestination = new CustomSwitchDestination(NoMatchFound);
void CustomSwitch(string value)
{
foreach (var switchOption in customSwitchList)
if (switchOption.Key.Equals(value, StringComparison.InvariantCultureIgnoreCase))
{
switchOption.Value.Invoke();
return;
}
defaultSwitchDestination.Invoke();
}
Of course, you will probably want to add some standard parameters and possibly a return type to the CustomSwitchDestination delegate. And you'll want to make better names!
If the behavior of each of your cases is not amenable to delegate invocation in this manner, such as if differnt parameters are necessary, then you’re stuck with chained if statments. I’ve also done this a few times.
if (s.Equals("house", StringComparison.InvariantCultureIgnoreCase))
{
s = "window";
}
else if (s.Equals("business", StringComparison.InvariantCultureIgnoreCase))
{
s = "really big window";
}
else if (s.Equals("school", StringComparison.InvariantCultureIgnoreCase))
{
s = "broken window";
}
An extension to the answer by #STLDeveloperA. A new way to do statement evaluation without multiple if statements as of C# 7 is using the pattern matching switch statement, similar to the way #STLDeveloper though this way is switching on the variable being switched
string houseName = "house"; // value to be tested
string s;
switch (houseName)
{
case var name when string.Equals(name, "Bungalow", StringComparison.InvariantCultureIgnoreCase):
s = "Single glazed";
break;
case var name when string.Equals(name, "Church", StringComparison.InvariantCultureIgnoreCase):
s = "Stained glass";
break;
...
default:
s = "No windows (cold or dark)";
break;
}
The visual studio magazine has a nice article on pattern matching case blocks that might be worth a look.
In some cases it might be a good idea to use an enum. So first parse the enum (with ignoreCase flag true) and than have a switch on the enum.
SampleEnum Result;
bool Success = SampleEnum.TryParse(inputText, true, out Result);
if(!Success){
//value was not in the enum values
}else{
switch (Result) {
case SampleEnum.Value1:
break;
case SampleEnum.Value2:
break;
default:
//do default behaviour
break;
}
}
One possible way would be to use an ignore case dictionary with an action delegate.
string s = null;
var dic = new Dictionary<string, Action>(StringComparer.CurrentCultureIgnoreCase)
{
{"house", () => s = "window"},
{"house2", () => s = "window2"}
};
dic["HouSe"]();
// Note that the call doesn't return text, but only populates local variable s.
// If you want to return the actual text, replace Action to Func<string> and values in dictionary to something like () => "window2"
Here's a solution that wraps #Magnus 's solution in a class:
public class SwitchCaseIndependent : IEnumerable<KeyValuePair<string, Action>>
{
private readonly Dictionary<string, Action> _cases = new Dictionary<string, Action>(StringComparer.OrdinalIgnoreCase);
public void Add(string theCase, Action theResult)
{
_cases.Add(theCase, theResult);
}
public Action this[string whichCase]
{
get
{
if (!_cases.ContainsKey(whichCase))
{
throw new ArgumentException($"Error in SwitchCaseIndependent, \"{whichCase}\" is not a valid option");
}
//otherwise
return _cases[whichCase];
}
}
public IEnumerator<KeyValuePair<string, Action>> GetEnumerator()
{
return _cases.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _cases.GetEnumerator();
}
}
Here's an example of using it in a simple Windows Form's app:
var mySwitch = new SwitchCaseIndependent
{
{"hello", () => MessageBox.Show("hello")},
{"Goodbye", () => MessageBox.Show("Goodbye")},
{"SoLong", () => MessageBox.Show("SoLong")},
};
mySwitch["HELLO"]();
If you use lambdas (like the example), you get closures which will capture your local variables (pretty close to the feeling you get from a switch statement).
Since it uses a Dictionary under the covers, it gets O(1) behavior and doesn't rely on walking through the list of strings. Of course, you need to construct that dictionary, and that probably costs more. If you want to reuse the Switch behavior over and over, you can create and initialize the the SwitchCaseIndependent object once and then use it as many times as you want.
It would probably make sense to add a simple bool ContainsCase(string aCase) method that simply calls the dictionary's ContainsKey method.
I would say that with switch expressions (added in C# 8.0), discard patterns and local functions the approaches suggested by #STLDev and #LewisM can be rewritten in even more clean/shorter way:
string houseName = "house"; // value to be tested
// local method to compare, I prefer to put them at the bottom of the invoking method:
bool Compare(string right) => string.Equals(houseName, right, StringComparison.InvariantCultureIgnoreCase);
var s = houseName switch
{
_ when Compare("Bungalow") => "Single glazed",
_ when Compare("Church") => "Stained glass",
// ...
_ => "No windows (cold or dark)" // default value
};
It should be sufficient to do this:
string s = "houSe";
switch (s.ToLowerInvariant())
{
case "house": s = "window";
break;
}
The switch comparison is thereby culture invariant. As far as I can see this should achieve the same result as the C#7 Pattern-Matching solutions, but more succinctly.
I hope this helps try to convert the whole string into particular case either lower case or Upper case and use the Lowercase string for comparison:
public string ConvertMeasurements(string unitType, string value)
{
switch (unitType.ToLower())
{
case "mmol/l": return (Double.Parse(value) * 0.0555).ToString();
case "mg/dl": return (double.Parse(value) * 18.0182).ToString();
}
}
Using the Case Insensitive Comparison:
Comparing strings while ignoring case.
switch (caseSwitch)
{
case string s when s.Equals("someValue", StringComparison.InvariantCultureIgnoreCase):
// ...
break;
}
for more detail Visit this link: Switch Case When In C# Statement And Expression
Now you can use the switch expression (rewrote the previous example):
return houseName switch
{
_ when houseName.Equals("MyHouse", StringComparison.InvariantCultureIgnoreCase) => "MyWindow",
_ when houseName.Equals("YourHouse", StringComparison.InvariantCultureIgnoreCase) => "YourWindow",
_ when houseName.Equals("House", StringComparison.InvariantCultureIgnoreCase) => "Window",
_ => null
};

What is quicker, switch on string or elseif on type?

Lets say I have the option of identifying a code path to take on the basis of a string comparison or else iffing the type:
Which is quicker and why?
switch(childNode.Name)
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
if(childNode is Bob)
{
}
elseif(childNode is Jill)
{
}
else if(childNode is Marko)
{
}
Update: The main reason I ask this is because the switch statement is perculiar about what counts as a case. For example it wont allow you to use variables, only constants which get moved to the main assembly. I assumed it had this restriction due to some funky stuff it was doing. If it is only translating to elseifs (as one poster commented) then why are we not allowed variables in case statements?
Caveat: I am post-optimising. This method is called many times in a slow part of the app.
Greg's profile results are great for the exact scenario he covered, but interestingly, the relative costs of the different methods change dramatically when considering a number of different factors including the number of types being compared, and the relative frequency and any patterns in the underlying data.
The simple answer is that nobody can tell you what the performance difference is going to be in your specific scenario, you will need to measure the performance in different ways yourself in your own system to get an accurate answer.
The If/Else chain is an effective approach for a small number of type comparisons, or if you can reliably predict which few types are going to make up the majority of the ones that you see. The potential problem with the approach is that as the number of types increases, the number of comparisons that must be executed increases as well.
if I execute the following:
int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ...
each of the previous if conditions must be evaluated before the correct block is entered. On the other hand
switch(value) {
case 0:...break;
case 1:...break;
case 2:...break;
...
case 25124:...break;
}
will perform one simple jump to the correct bit of code.
Where it gets more complicated in your example is that your other method uses a switch on strings rather than integers which gets a little more complicated. At a low level, strings can't be switched on in the same way that integer values can so the C# compiler does some magic to make this work for you.
If the switch statement is "small enough" (where the compiler does what it thinks is best automatically) switching on strings generates code that is the same as an if/else chain.
switch(someString) {
case "Foo": DoFoo(); break;
case "Bar": DoBar(); break;
default: DoOther; break;
}
is the same as:
if(someString == "Foo") {
DoFoo();
} else if(someString == "Bar") {
DoBar();
} else {
DoOther();
}
Once the list of items in the dictionary gets "big enough" the compiler will automatically create an internal dictionary that maps from the strings in the switch to an integer index and then a switch based on that index.
It looks something like this (Just imagine more entries than I am going to bother to type)
A static field is defined in a "hidden" location that is associated with the class containing the switch statement of type Dictionary<string, int> and given a mangled name
//Make sure the dictionary is loaded
if(theDictionary == null) {
//This is simplified for clarity, the actual implementation is more complex
// in order to ensure thread safety
theDictionary = new Dictionary<string,int>();
theDictionary["Foo"] = 0;
theDictionary["Bar"] = 1;
}
int switchIndex;
if(theDictionary.TryGetValue(someString, out switchIndex)) {
switch(switchIndex) {
case 0: DoFoo(); break;
case 1: DoBar(); break;
}
} else {
DoOther();
}
In some quick tests that I just ran, the If/Else method is about 3x as fast as the switch for 3 different types (where the types are randomly distributed). At 25 types the switch is faster by a small margin (16%) at 50 types the switch is more than twice as fast.
If you are going to be switching on a large number of types, I would suggest a 3rd method:
private delegate void NodeHandler(ChildNode node);
static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher = CreateSwitcher();
private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher()
{
var ret = new Dictionary<RuntimeTypeHandle, NodeHandler>();
ret[typeof(Bob).TypeHandle] = HandleBob;
ret[typeof(Jill).TypeHandle] = HandleJill;
ret[typeof(Marko).TypeHandle] = HandleMarko;
return ret;
}
void HandleChildNode(ChildNode node)
{
NodeHandler handler;
if (TaskHandleSwitcher.TryGetValue(Type.GetRuntimeType(node), out handler))
{
handler(node);
}
else
{
//Unexpected type...
}
}
This is similar to what Ted Elliot suggested, but the usage of runtime type handles instead of full type objects avoids the overhead of loading the type object through reflection.
Here are some quick timings on my machine:
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 5 types
Method Time % of optimal
If/Else 179.67 100.00
TypeHandleDictionary 321.33 178.85
TypeDictionary 377.67 210.20
Switch 492.67 274.21
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 10 types
Method Time % of optimal
If/Else 271.33 100.00
TypeHandleDictionary 312.00 114.99
TypeDictionary 374.33 137.96
Switch 490.33 180.71
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 15 types
Method Time % of optimal
TypeHandleDictionary 312.00 100.00
If/Else 369.00 118.27
TypeDictionary 371.67 119.12
Switch 491.67 157.59
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 20 types
Method Time % of optimal
TypeHandleDictionary 335.33 100.00
TypeDictionary 373.00 111.23
If/Else 462.67 137.97
Switch 490.33 146.22
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 25 types
Method Time % of optimal
TypeHandleDictionary 319.33 100.00
TypeDictionary 371.00 116.18
Switch 483.00 151.25
If/Else 562.00 175.99
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 50 types
Method Time % of optimal
TypeHandleDictionary 319.67 100.00
TypeDictionary 376.67 117.83
Switch 453.33 141.81
If/Else 1,032.67 323.04
On my machine at least, the type handle dictionary approach beats all of the others for anything over 15 different types when the distribution
of the types used as input to the method is random.
If on the other hand, the input is composed entirely of the type that is checked first in the if/else chain that method is much faster:
Testing 3 iterations with 5,000,000 data elements (mode=UniformFirst) and 50 types
Method Time % of optimal
If/Else 39.00 100.00
TypeHandleDictionary 317.33 813.68
TypeDictionary 396.00 1,015.38
Switch 403.00 1,033.33
Conversely, if the input is always the last thing in the if/else chain, it has the opposite effect:
Testing 3 iterations with 5,000,000 data elements (mode=UniformLast) and 50 types
Method Time % of optimal
TypeHandleDictionary 317.67 100.00
Switch 354.33 111.54
TypeDictionary 377.67 118.89
If/Else 1,907.67 600.52
If you can make some assumptions about your input, you might get the best performance from a hybrid approach where you perform if/else checks for the few types that are most common, and then fall back to a dictionary-driven approach if those fail.
Firstly, you're comparing apples and oranges. You'd first need to compare switch on type vs switch on string, and then if on type vs if on string, and then compare the winners.
Secondly, this is the kind of thing OO was designed for. In languages that support OO, switching on type (of any kind) is a code smell that points to poor design. The solution is to derive from a common base with an abstract or virtual method (or a similar construct, depending on your language)
eg.
class Node
{
public virtual void Action()
{
// Perform default action
}
}
class Bob : Node
{
public override void Action()
{
// Perform action for Bill
}
}
class Jill : Node
{
public override void Action()
{
// Perform action for Jill
}
}
Then, instead of doing the switch statement, you just call childNode.Action()
I just implemented a quick test application and profiled it with ANTS 4.
Spec: .Net 3.5 sp1 in 32bit Windows XP, code built in release mode.
3 million tests:
Switch: 1.842 seconds
If: 0.344 seconds.
Furthermore, the switch statement results reveal (unsurprisingly) that longer names take longer.
1 million tests
Bob: 0.612 seconds.
Jill: 0.835 seconds.
Marko: 1.093 seconds.
I looks like the "If Else" is faster, at least the the scenario I created.
class Program
{
static void Main( string[] args )
{
Bob bob = new Bob();
Jill jill = new Jill();
Marko marko = new Marko();
for( int i = 0; i < 1000000; i++ )
{
Test( bob );
Test( jill );
Test( marko );
}
}
public static void Test( ChildNode childNode )
{
TestSwitch( childNode );
TestIfElse( childNode );
}
private static void TestIfElse( ChildNode childNode )
{
if( childNode is Bob ){}
else if( childNode is Jill ){}
else if( childNode is Marko ){}
}
private static void TestSwitch( ChildNode childNode )
{
switch( childNode.Name )
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
}
}
class ChildNode { public string Name { get; set; } }
class Bob : ChildNode { public Bob(){ this.Name = "Bob"; }}
class Jill : ChildNode{public Jill(){this.Name = "Jill";}}
class Marko : ChildNode{public Marko(){this.Name = "Marko";}}
Switch statement is faster to execute than the if-else-if ladder. This is due to the compiler's ability to optimise the switch statement. In the case of the if-else-if ladder, the code must process each if statement in the order determined by the programmer. However, because each case within a switch statement does not rely on earlier cases, the compiler is able to re-order the testing in such a way as to provide the fastest execution.
If you've got the classes made, I'd suggest using a Strategy design pattern instead of switch or elseif.
Try using enumerations for each object, you can switch on enums quickly and easily.
Unless you've already written this and find you have a performance problem I wouldn't worry about which is quicker. Go with the one that's more readable. Remember, "Premature optimization is the root of all evil." - Donald Knuth
A SWITCH construct was originally intended for integer data; it's intent was to use the argument directly as a index into a "dispatch table", a table of pointers. As such, there would be a single test, then launch directly to the relevant code, rather than a series of tests.
The difficulty here is that it's use has been generalized to "string" types, which obviously cannot be used as an index, and all advantage of the SWITCH construct is lost.
If speed is your intended goal, the problem is NOT your code, but your data structure. If the "name" space is as simple as you show it, better to code it into an integer value (when data is created, for example), and use this integer in the "many times in a slow part of the app".
If the types you're switching on are primitive .NET types you can use Type.GetTypeCode(Type), but if they're custom types they will all come back as TypeCode.Object.
A dictionary with delegates or handler classes might work as well.
Dictionary<Type, HandlerDelegate> handlers = new Dictionary<Type, HandlerDelegate>();
handlers[typeof(Bob)] = this.HandleBob;
handlers[typeof(Jill)] = this.HandleJill;
handlers[typeof(Marko)] = this.HandleMarko;
handlers[childNode.GetType()](childNode);
/// ...
private void HandleBob(Node childNode) {
// code to handle Bob
}
The switch() will compile out to code equivalent to a set of else ifs. The string comparisons will be much slower than the type comparisons.
I recall reading in several reference books that the if/else branching is quicker than the switch statement. However, a bit of research on Blackwasp shows that the switch statement is actually faster:
http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
In reality, if you're comparing the typical 3 to 10 (or so) statements, I seriously doubt there's any real performance gain using one or the other.
As Chris has already said, go for readability:
What is quicker, switch on string or elseif on type?
I think the main performance issue here is, that in the switch block, you compare strings, and that in the if-else block, you check for types... Those two are not the same, and therefore, I'd say you're "comparing potatoes to bananas".
I'd start by comparing this:
switch(childNode.Name)
{
case "Bob":
break;
case "Jill":
break;
case "Marko":
break;
}
if(childNode.Name == "Bob")
{}
else if(childNode.Name == "Jill")
{}
else if(childNode.Name == "Marko")
{}
I'm not sure how faster it could be the right design would be to go for polymorphism.
interface INode
{
void Action;
}
class Bob : INode
{
public void Action
{
}
}
class Jill : INode
{
public void Action
{
}
}
class Marko : INode
{
public void Action
{
}
}
//Your function:
void Do(INode childNode)
{
childNode.Action();
}
Seeing what your switch statement does will help better. If your function is not really anything about an action on the type, may be you could define an enum on each type.
enum NodeType { Bob, Jill, Marko, Default }
interface INode
{
NodeType Node { get; };
}
class Bob : INode
{
public NodeType Node { get { return NodeType.Bob; } }
}
class Jill : INode
{
public NodeType Node { get { return NodeType.Jill; } }
}
class Marko : INode
{
public NodeType Node { get { return NodeType.Marko; } }
}
//Your function:
void Do(INode childNode)
{
switch(childNode.Node)
{
case Bob:
break;
case Jill:
break;
case Marko:
break;
Default:
throw new ArgumentException();
}
}
I assume this has to be faster than both approaches in question. You might want to try abstract class route if nanoseconds does matter for you.
I created a little console to show my solution, just to highlight the speed difference. I used a different string hash algorithm as the certificate version is to slow for me on runtime and duplicates are unlikely and if so my switch statement would fail (never happened till now). My unique hash extension method is included in the code below.
I will take 29 ticks over 695 ticks any time, specially when using critical code.
With a set of strings from a given database you can create a small application to create the constant in a given file for you to use in your code, if values are added you just re-run your batch and constants are generated and picked up by the solution.
public static class StringExtention
{
public static long ToUniqueHash(this string text)
{
long value = 0;
var array = text.ToCharArray();
unchecked
{
for (int i = 0; i < array.Length; i++)
{
value = (value * 397) ^ array[i].GetHashCode();
value = (value * 397) ^ i;
}
return value;
}
}
}
public class AccountTypes
{
static void Main()
{
var sb = new StringBuilder();
sb.AppendLine($"const long ACCOUNT_TYPE = {"AccountType".ToUniqueHash()};");
sb.AppendLine($"const long NET_LIQUIDATION = {"NetLiquidation".ToUniqueHash()};");
sb.AppendLine($"const long TOTAL_CASH_VALUE = {"TotalCashValue".ToUniqueHash()};");
sb.AppendLine($"const long SETTLED_CASH = {"SettledCash".ToUniqueHash()};");
sb.AppendLine($"const long ACCRUED_CASH = {"AccruedCash".ToUniqueHash()};");
sb.AppendLine($"const long BUYING_POWER = {"BuyingPower".ToUniqueHash()};");
sb.AppendLine($"const long EQUITY_WITH_LOAN_VALUE = {"EquityWithLoanValue".ToUniqueHash()};");
sb.AppendLine($"const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = {"PreviousEquityWithLoanValue".ToUniqueHash()};");
sb.AppendLine($"const long GROSS_POSITION_VALUE ={ "GrossPositionValue".ToUniqueHash()};");
sb.AppendLine($"const long REQT_EQUITY = {"ReqTEquity".ToUniqueHash()};");
sb.AppendLine($"const long REQT_MARGIN = {"ReqTMargin".ToUniqueHash()};");
sb.AppendLine($"const long SPECIAL_MEMORANDUM_ACCOUNT = {"SMA".ToUniqueHash()};");
sb.AppendLine($"const long INIT_MARGIN_REQ = { "InitMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long MAINT_MARGIN_REQ = {"MaintMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long AVAILABLE_FUNDS = {"AvailableFunds".ToUniqueHash()};");
sb.AppendLine($"const long EXCESS_LIQUIDITY = {"ExcessLiquidity".ToUniqueHash()};");
sb.AppendLine($"const long CUSHION = {"Cushion".ToUniqueHash()};");
sb.AppendLine($"const long FULL_INIT_MARGIN_REQ = {"FullInitMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long FULL_MAINTMARGIN_REQ ={ "FullMaintMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long FULL_AVAILABLE_FUNDS = {"FullAvailableFunds".ToUniqueHash()};");
sb.AppendLine($"const long FULL_EXCESS_LIQUIDITY ={ "FullExcessLiquidity".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_INIT_MARGIN_REQ = {"LookAheadInitMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_MAINT_MARGIN_REQ = {"LookAheadMaintMarginReq".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_AVAILABLE_FUNDS = {"LookAheadAvailableFunds".ToUniqueHash()};");
sb.AppendLine($"const long LOOK_AHEAD_EXCESS_LIQUIDITY = {"LookAheadExcessLiquidity".ToUniqueHash()};");
sb.AppendLine($"const long HIGHEST_SEVERITY = {"HighestSeverity".ToUniqueHash()};");
sb.AppendLine($"const long DAY_TRADES_REMAINING = {"DayTradesRemaining".ToUniqueHash()};");
sb.AppendLine($"const long LEVERAGE = {"Leverage".ToUniqueHash()};");
Console.WriteLine(sb.ToString());
Test();
}
public static void Test()
{
//generated constant values
const long ACCOUNT_TYPE = -3012481629590703298;
const long NET_LIQUIDATION = 5886477638280951639;
const long TOTAL_CASH_VALUE = 2715174589598334721;
const long SETTLED_CASH = 9013818865418133625;
const long ACCRUED_CASH = -1095823472425902515;
const long BUYING_POWER = -4447052054809609098;
const long EQUITY_WITH_LOAN_VALUE = -4088154623329785565;
const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = 6224054330592996694;
const long GROSS_POSITION_VALUE = -7316842993788269735;
const long REQT_EQUITY = -7457439202928979430;
const long REQT_MARGIN = -7525806483981945115;
const long SPECIAL_MEMORANDUM_ACCOUNT = -1696406879233404584;
const long INIT_MARGIN_REQ = 4495254338330797326;
const long MAINT_MARGIN_REQ = 3923858659879350034;
const long AVAILABLE_FUNDS = 2736927433442081110;
const long EXCESS_LIQUIDITY = 5975045739561521360;
const long CUSHION = 5079153439662500166;
const long FULL_INIT_MARGIN_REQ = -6446443340724968443;
const long FULL_MAINTMARGIN_REQ = -8084126626285123011;
const long FULL_AVAILABLE_FUNDS = 1594040062751632873;
const long FULL_EXCESS_LIQUIDITY = -2360941491690082189;
const long LOOK_AHEAD_INIT_MARGIN_REQ = 5230305572167766821;
const long LOOK_AHEAD_MAINT_MARGIN_REQ = 4895875570930256738;
const long LOOK_AHEAD_AVAILABLE_FUNDS = -7687608210548571554;
const long LOOK_AHEAD_EXCESS_LIQUIDITY = -4299898188451362207;
const long HIGHEST_SEVERITY = 5831097798646393988;
const long DAY_TRADES_REMAINING = 3899479916235857560;
const long LEVERAGE = 1018053116254258495;
bool found = false;
var sValues = new string[] {
"AccountType"
,"NetLiquidation"
,"TotalCashValue"
,"SettledCash"
,"AccruedCash"
,"BuyingPower"
,"EquityWithLoanValue"
,"PreviousEquityWithLoanValue"
,"GrossPositionValue"
,"ReqTEquity"
,"ReqTMargin"
,"SMA"
,"InitMarginReq"
,"MaintMarginReq"
,"AvailableFunds"
,"ExcessLiquidity"
,"Cushion"
,"FullInitMarginReq"
,"FullMaintMarginReq"
,"FullAvailableFunds"
,"FullExcessLiquidity"
,"LookAheadInitMarginReq"
,"LookAheadMaintMarginReq"
,"LookAheadAvailableFunds"
,"LookAheadExcessLiquidity"
,"HighestSeverity"
,"DayTradesRemaining"
,"Leverage"
};
long t1, t2;
var sw = System.Diagnostics.Stopwatch.StartNew();
foreach (var name in sValues)
{
switch (name)
{
case "AccountType": found = true; break;
case "NetLiquidation": found = true; break;
case "TotalCashValue": found = true; break;
case "SettledCash": found = true; break;
case "AccruedCash": found = true; break;
case "BuyingPower": found = true; break;
case "EquityWithLoanValue": found = true; break;
case "PreviousEquityWithLoanValue": found = true; break;
case "GrossPositionValue": found = true; break;
case "ReqTEquity": found = true; break;
case "ReqTMargin": found = true; break;
case "SMA": found = true; break;
case "InitMarginReq": found = true; break;
case "MaintMarginReq": found = true; break;
case "AvailableFunds": found = true; break;
case "ExcessLiquidity": found = true; break;
case "Cushion": found = true; break;
case "FullInitMarginReq": found = true; break;
case "FullMaintMarginReq": found = true; break;
case "FullAvailableFunds": found = true; break;
case "FullExcessLiquidity": found = true; break;
case "LookAheadInitMarginReq": found = true; break;
case "LookAheadMaintMarginReq": found = true; break;
case "LookAheadAvailableFunds": found = true; break;
case "LookAheadExcessLiquidity": found = true; break;
case "HighestSeverity": found = true; break;
case "DayTradesRemaining": found = true; break;
case "Leverage": found = true; break;
default: found = false; break;
}
if (!found)
throw new NotImplementedException();
}
t1 = sw.ElapsedTicks;
sw.Restart();
foreach (var name in sValues)
{
switch (name.ToUniqueHash())
{
case ACCOUNT_TYPE:
found = true;
break;
case NET_LIQUIDATION:
found = true;
break;
case TOTAL_CASH_VALUE:
found = true;
break;
case SETTLED_CASH:
found = true;
break;
case ACCRUED_CASH:
found = true;
break;
case BUYING_POWER:
found = true;
break;
case EQUITY_WITH_LOAN_VALUE:
found = true;
break;
case PREVIOUS_EQUITY_WITH_LOAN_VALUE:
found = true;
break;
case GROSS_POSITION_VALUE:
found = true;
break;
case REQT_EQUITY:
found = true;
break;
case REQT_MARGIN:
found = true;
break;
case SPECIAL_MEMORANDUM_ACCOUNT:
found = true;
break;
case INIT_MARGIN_REQ:
found = true;
break;
case MAINT_MARGIN_REQ:
found = true;
break;
case AVAILABLE_FUNDS:
found = true;
break;
case EXCESS_LIQUIDITY:
found = true;
break;
case CUSHION:
found = true;
break;
case FULL_INIT_MARGIN_REQ:
found = true;
break;
case FULL_MAINTMARGIN_REQ:
found = true;
break;
case FULL_AVAILABLE_FUNDS:
found = true;
break;
case FULL_EXCESS_LIQUIDITY:
found = true;
break;
case LOOK_AHEAD_INIT_MARGIN_REQ:
found = true;
break;
case LOOK_AHEAD_MAINT_MARGIN_REQ:
found = true;
break;
case LOOK_AHEAD_AVAILABLE_FUNDS:
found = true;
break;
case LOOK_AHEAD_EXCESS_LIQUIDITY:
found = true;
break;
case HIGHEST_SEVERITY:
found = true;
break;
case DAY_TRADES_REMAINING:
found = true;
break;
case LEVERAGE:
found = true;
break;
default:
found = false;
break;
}
if (!found)
throw new NotImplementedException();
}
t2 = sw.ElapsedTicks;
sw.Stop();
Console.WriteLine($"String switch:{t1:N0} long switch:{t2:N0}");
var faster = (t1 > t2) ? "Slower" : "faster";
Console.WriteLine($"String switch: is {faster} than long switch: by {Math.Abs(t1-t2)} Ticks");
Console.ReadLine();
}
well it depend on language you need to test yourself to see timing that which one is fast. like in php web language if / else if is fast compare to switch so you need to find it out by running some bench basic code in your desire language.
personally i prefer if / else if for code reading as switch statements can be nightmare to read where there is big code blocks in each condition as you will have to look for break keywords it each end point manually while with if / else if due to the start and end braces its easy to trace code blocks.
php
String comparison will always rely completely on the runtime environment (unless the strings are statically allocated, though the need to compare those to each other is debatable). Type comparison, however, can be done through dynamic or static binding, and either way it's more efficient for the runtime environment than comparing individual characters in a string.
Surely the switch on String would compile down to a String comparison (one per case) which is slower than a type comparison (and far slower than the typical integer compare that is used for switch/case)?
Three thoughts:
1) If you're going to do something different based on the types of the objects, it might make sense to move that behavior into those classes. Then instead of switch or if-else, you'd just call childNode.DoSomething().
2) Comparing types will be much faster than string comparisons.
3) In the if-else design, you might be able to take advantage of reordering the tests. If "Jill" objects make up 90% of the objects going through there, test for them first.
One of the issues you have with the switch is using strings, like "Bob", this will cause a lot more cycles and lines in the compiled code. The IL that is generated will have to declare a string, set it to "Bob" then use it in the comparison. So with that in mind your IF statements will run faster.
PS. Aeon's example wont work because you can't switch on Types. (No I don't know why exactly, but we've tried it an it doesn't work. It has to do with the type being variable)
If you want to test this, just build a separate application and build two simple Methods that do what is written up above and use something like Ildasm.exe to see the IL. You'll notice a lot less lines in the IF statement Method's IL.
Ildasm comes with VisualStudio...
ILDASM page - http://msdn.microsoft.com/en-us/library/f7dy01k1(VS.80).aspx
ILDASM Tutorial - http://msdn.microsoft.com/en-us/library/aa309387(VS.71).aspx
Remember, the profiler is your friend. Any guesswork is a waste of time most of the time.
BTW, I have had a good experience with JetBrains' dotTrace profiler.
Switch on string basically gets compiled into a if-else-if ladder. Try decompiling a simple one. In any case, testing string equailty should be cheaper since they are interned and all that would be needed is a reference check. Do what makes sense in terms of maintainability; if you are compring strings, do the string switch. If you are selecting based on type, a type ladder is the more appropriate.
I kind of do it a bit different,
The strings you're switching on are going to be constants, so you can predict the values at compile time.
in your case i'd use the hash values, this is an int switch, you have 2 options, use compile time constants or calculate at run-time.
//somewhere in your code
static long _bob = "Bob".GetUniqueHashCode();
static long _jill = "Jill".GetUniqueHashCode();
static long _marko = "Marko".GeUniquetHashCode();
void MyMethod()
{
...
if(childNode.Tag==0)
childNode.Tag= childNode.Name.GetUniquetHashCode()
switch(childNode.Tag)
{
case _bob :
break;
case _jill :
break;
case _marko :
break;
}
}
The extension method for GetUniquetHashCode can be something like this:
public static class StringExtentions
{
/// <summary>
/// Return unique Int64 value for input string
/// </summary>
/// <param name="strText"></param>
/// <returns></returns>
public static Int64 GetUniquetHashCode(this string strText)
{
Int64 hashCode = 0;
if (!string.IsNullOrEmpty(strText))
{
//Unicode Encode Covering all character-set
byte[] byteContents = Encoding.Unicode.GetBytes(strText);
System.Security.Cryptography.SHA256 hash = new System.Security.Cryptography.SHA256CryptoServiceProvider();
byte[] hashText = hash.ComputeHash(byteContents);
//32Byte hashText separate
//hashCodeStart = 0~7 8Byte
//hashCodeMedium = 8~23 8Byte
//hashCodeEnd = 24~31 8Byte
//and Fold
Int64 hashCodeStart = BitConverter.ToInt64(hashText, 0);
Int64 hashCodeMedium = BitConverter.ToInt64(hashText, 8);
Int64 hashCodeEnd = BitConverter.ToInt64(hashText, 24);
hashCode = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
}
return (hashCode);
}
}
The source of this code was published here
Please note that using Cryptography is slow, you would typically warm-up the supported string on application start, i do this my saving them at static fields as will not change and are not instance relevant. please note that I set the tag value of the node object, I could use any property or add one, just make sure that these are in sync with the actual text.
I work on low latency systems and all my codes come as a string of command:value,command:value....
now the command are all known as 64 bit integer values so switching like this saves some CPU time.
I was just reading through the list of answers here, and wanted to share this benchmark test which compares the switch construct with the if-else and ternary ? operators.
What I like about that post is it not only compares single-left constructs (eg, if-else) but double and triple level constructs (eg, if-else-if-else).
According to the results, the if-else construct was the fastest in 8/9 test cases; the switch construct tied for the fastest in 5/9 test cases.
So if you're looking for speed if-else appears to be the fastest way to go.
I may be missing something, but couldn't you do a switch statement on the type instead of the String? That is,
switch(childNode.Type)
{
case Bob:
break;
case Jill:
break;
case Marko:
break;
}

Categories

Resources