Using .StartsWith in a Switch statement? - c#

I'm working on a Switch statement and with two of the conditions I need to see if the values start with a specific value. The Switch statement does like this. The error says "cannot covert type bool to string".
Anyone know if I can use the StartsWith in a Switch or do I need to use If...Else statements?
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
case subArea.StartsWith("3*"):
case subArea.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}

Since C# 7 you can do the following:
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
case string s when s.StartsWith("3*"):
return "123";
case string s when s.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}

EDIT: If you are using C# >= 7, take a look at this answer first.
You are switching a String, and subArea.StartsWith() returns a Boolean, that's why you can't do it. I suggest you do it like this:
if (subArea.StartsWith("3*") || subArea.StartsWith("03*"))
return "123";
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
default:
return "ABCXYZ123";
}
The result will be the same.

Thanks to the when clause, you can now do:
switch (subArea)
{
// Skipping regular cases with string literals
case string dummy
when subArea.StartsWith("3*") ||
subArea.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}

Since C# 9 you can do the following:
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
case { } when subArea.StartsWith("3*"):
case { } when subArea.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}

The case labels must be strings, since the switch expression is a string; however, StartsWith returns a Boolean. I suggest handling these special cases in the default section.
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
default:
if (subArea.StartsWith("3") || subArea.StartsWith("03")) {
return "123";
}
return "ABCXYZ123";
}
Also the star (*) is probably wrong, unless you want subArea to contain it. StartWith does not accept wildcards.
Alternatively you could use regex:
if (Regex.IsMatch(subArea, "^3|^03")) { // or "^(3|03)"
return "123";
}
where ^ means start of line and | means or.

Since C# 9 you can also do something like this, with a switch expression instead of a statement:
return subArea switch
{
"4100" or "4101" or "4102" or "4200" => "ABC",
"600A" => "XWZ",
{ } when subArea.StartsWith("3*") || subArea.StartsWith("03*") => "123",
_ => "ABCXYZ123"
};

Joe kind of beat me to it, but here's another non-switch way of doing it, which essentially implements a pattern matching algorithm with a rule set.
private static string GetSomeStringOrOther(string subArea)
{
// Create a set of pattern matching functions...
Func<string, string, bool> matchEquals = (a, b) => a.Equals(b);
Func<string, string, bool> matchStarts = (a, b) => a.StartsWith(b);
// Create a rule set...
Tuple<string, string, Func<string, string, bool>>[] cases = new []
{
new Tuple<string, string, Func<string, string, bool>>("4100", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("4101", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("4102", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("4200", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("600A", "XWZ", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("3*", "123", matchStarts),
new Tuple<string, string, Func<string, string, bool>>("03*", "123", matchStarts),
};
// Look for a match...
foreach(var matchCase in cases)
{
if(matchCase.Item3(subArea, matchCase.Item1))
{
// Return if it matches...
return matchCase.Item2;
}
}
// Otherwise return the default...
return "ABCXYZ123";
}
Advantages
If you need a new rule, it's easy to add to the rule set.
If you need a new pattern matching function, again, easy to add.
Doesn't need extensive rework if a rule changes.
Disadvantages
Novice/Beginner and even some intermediate developers might not have a clue what's going on.
Improvements
Replace Tuple<string, string, Func<string, string, bool>> with a semantic object that represents a Rule

Just for fun, here's another solution that avoids the switch statement.
var map = new[] {
new { Value = "4100", StartsWith = false, Result="ABC" },
new { Value = "4101", StartsWith = false, Result="ABC" },
new { Value = "4102", StartsWith = false, Result="ABC" },
new { Value = "4200", StartsWith = false, Result="ABC" },
new { Value = "600A", StartsWith = false, Result="XWZ" },
new { Value = "3*", StartsWith = true, Result="123" },
new { Value = "03*", StartsWith = true, Result="123" },
};
var subarea = ... whatever ...;
var result = map.Where(e =>
{
if (e.StartsWith)
{
return subarea.StartsWith(e.Value);
}
else
{
return subarea == e.Value;
}
}
)
.Select(e => e.Result)
.FirstOrDefault() ?? "ABCXZ123";
The order in the array map determines the priority, so that, for example, you can have an exact match on, say, "3*11", as well as a StartsWith match on "3*", e.g.:
var map = new[] {
new { Value = "3*11", StartsWith = false, Result="ABC" },
new { Value = "4100", StartsWith = false, Result="ABC" },
new { Value = "4101", StartsWith = false, Result="ABC" },
new { Value = "4102", StartsWith = false, Result="ABC" },
new { Value = "4200", StartsWith = false, Result="ABC" },
new { Value = "600A", StartsWith = false, Result="XWZ" },
new { Value = "3*", StartsWith = true, Result="123" },
new { Value = "03*", StartsWith = true, Result="123" },
};

With LINQ, the nice answer by #seriesOne can be "simplified" a bit by replacing the foreach and return statements with:
// using System.Linq;
// Look for a match...
var result = cases
.Where(c => c.Item3(subArea, c.Item1))
.FirstOrDefault();
// Return the match or the default.
return result == null ? "ABCXYZ123" : result.Item2;

Related

Is there a way I can replace a switch statement with actions by using a dictionary and some other code?

Here is what my app has:
private void saveInDatabase(string key, string value)
switch (key)
{
case "a":
def = Helpers.Utils.listOfDoubleFromString(value);
break;
case "b":
chi = int.Parse(value);
break;
....
}
Is there a way that I could use a dictionary like this to decide what actions happen with the different case values?
{"a", () => def = Helpers.Utils.listOfDoubleFromString(value)},
{"b", () => chi = int.Parse(value)},
My app has a large number of these case statements so I am interested to know if I could replace them by setting up a dictionary and then some more code.
I mean you pretty much got it:
string def;
int chi;
var map = new Dictionary<string, Action<string>>
{
["a"] = value => def = Helpers.Utils.listOfDoubleFromString(value),
["b"] = value => chi = int.Parse(value),
};
map[key](value);

Passing a parameter into an anonymous Func

The following code outputs bee:
var str = "B";
var env = new Func<string>(() => {
switch (str)
{
case "A":
return "aye";
case "B":
return "bee";
default:
return "see";
}
}).Invoke();
Console.WriteLine(env);
How can I pass the str variable as a parameter to the anonymous function? The closest I can get is this:
var str = "B";
Func<string, string> env = a => {
switch (a)
{
case "A":
return "aye";
case "B":
return "bee";
default:
return "see";
}
};
Console.WriteLine(env(str));
But that is not anonymous, as it is named env.
Is it possible to use the first form and still pass in a parameter?

C# Switch-case string end with

Is there any way to make a case condition in a switch statement where you say if a string end with something?
switch (Pac.Sku)
{
case "A":
pacVM.Sucursal = "Managua";
break;
case "B":
pacVM.Sucursal = "Masaya";
break;
case "C":
pacVM.Sucursal = "Leon";
break;
default:
pacVM.Sucursal = "N/A";
break;
}
Get the last character of the string, and switch over the result:
switch (Pac.Sku.Last())
{
case 'A':
pacVM.Sucursal = "Managua";
break;
case 'B':
pacVM.Sucursal = "Masaya";
break;
case 'C':
pacVM.Sucursal = "Leon";
break;
default:
pacVM.Sucursal = "N/A";
break;
}
If the string could be null or empty use something like this function instead of Last(). This function returns null if the string is null, null if the string is empty, and the last character of the string if it is not null or empty:
char? GetLast(string s)
{
return s?.Length > 0 ? s.Last() : (char?)null;
}
Switch:
switch(GetLast(Pac.Sku))
You can
use pattern matching feature of C# 7.0 to achieve this. Here is a very basic example:
var t = "blah";
switch (t)
{
case var a when t.EndsWith("bl"):
Console.WriteLine("I'm not here");
break;
case var b when t.EndsWith("ah"):
Console.WriteLine("I'm here");
break;
}
You can get creative with a Func<string, string>[] like this:
Func<string, string>[] cases = new Func<string, string>[]
{
x => x.EndsWith("A") ? "Managua" : null,
x => x.EndsWith("B") ? "Masaya" : null,
x => x.EndsWith("C") ? "Leon" : null,
x => "N/A",
};
Func<string, string> #switch = cases.Aggregate((x, y) => z => x(z) ?? y(z));
string result = #switch(Pac.Sku);
I have tested this with sample input that matches each of the cases and it works just fine.
One significant advantage with this approach is that you can build the Func<string, string>[] at run-time. Nice for creating configurable solutions.
You're also not limited to just using EndsWith - any condition can be used that suits the purpose.
I think it's not a way!
You can only use the if-else
if (Pac.Sku.EndsWith("A") )
{
pacVM.Sucursal= "Managua";
}
else if (Pac.Sku.EndsWith("B"))
{
pacVM.Sucursal= "Masaya";
}
else if (Pac.Sku.EndsWith("C"))
{
pacVM.Sucursal= "Leon";
}
else
{
pacVM.Sucursal= "N/A";
}

Switch string contains or startswith [duplicate]

I'm working on a Switch statement and with two of the conditions I need to see if the values start with a specific value. The Switch statement does like this. The error says "cannot covert type bool to string".
Anyone know if I can use the StartsWith in a Switch or do I need to use If...Else statements?
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
case subArea.StartsWith("3*"):
case subArea.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}
Since C# 7 you can do the following:
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
case string s when s.StartsWith("3*"):
return "123";
case string s when s.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}
EDIT: If you are using C# >= 7, take a look at this answer first.
You are switching a String, and subArea.StartsWith() returns a Boolean, that's why you can't do it. I suggest you do it like this:
if (subArea.StartsWith("3*") || subArea.StartsWith("03*"))
return "123";
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
default:
return "ABCXYZ123";
}
The result will be the same.
Thanks to the when clause, you can now do:
switch (subArea)
{
// Skipping regular cases with string literals
case string dummy
when subArea.StartsWith("3*") ||
subArea.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}
Since C# 9 you can do the following:
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
case { } when subArea.StartsWith("3*"):
case { } when subArea.StartsWith("03*"):
return "123";
default:
return "ABCXYZ123";
}
The case labels must be strings, since the switch expression is a string; however, StartsWith returns a Boolean. I suggest handling these special cases in the default section.
switch(subArea)
{
case "4100":
case "4101":
case "4102":
case "4200":
return "ABC";
case "600A":
return "XWZ";
default:
if (subArea.StartsWith("3") || subArea.StartsWith("03")) {
return "123";
}
return "ABCXYZ123";
}
Also the star (*) is probably wrong, unless you want subArea to contain it. StartWith does not accept wildcards.
Alternatively you could use regex:
if (Regex.IsMatch(subArea, "^3|^03")) { // or "^(3|03)"
return "123";
}
where ^ means start of line and | means or.
Since C# 9 you can also do something like this, with a switch expression instead of a statement:
return subArea switch
{
"4100" or "4101" or "4102" or "4200" => "ABC",
"600A" => "XWZ",
{ } when subArea.StartsWith("3*") || subArea.StartsWith("03*") => "123",
_ => "ABCXYZ123"
};
Joe kind of beat me to it, but here's another non-switch way of doing it, which essentially implements a pattern matching algorithm with a rule set.
private static string GetSomeStringOrOther(string subArea)
{
// Create a set of pattern matching functions...
Func<string, string, bool> matchEquals = (a, b) => a.Equals(b);
Func<string, string, bool> matchStarts = (a, b) => a.StartsWith(b);
// Create a rule set...
Tuple<string, string, Func<string, string, bool>>[] cases = new []
{
new Tuple<string, string, Func<string, string, bool>>("4100", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("4101", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("4102", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("4200", "ABC", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("600A", "XWZ", matchEquals),
new Tuple<string, string, Func<string, string, bool>>("3*", "123", matchStarts),
new Tuple<string, string, Func<string, string, bool>>("03*", "123", matchStarts),
};
// Look for a match...
foreach(var matchCase in cases)
{
if(matchCase.Item3(subArea, matchCase.Item1))
{
// Return if it matches...
return matchCase.Item2;
}
}
// Otherwise return the default...
return "ABCXYZ123";
}
Advantages
If you need a new rule, it's easy to add to the rule set.
If you need a new pattern matching function, again, easy to add.
Doesn't need extensive rework if a rule changes.
Disadvantages
Novice/Beginner and even some intermediate developers might not have a clue what's going on.
Improvements
Replace Tuple<string, string, Func<string, string, bool>> with a semantic object that represents a Rule
Just for fun, here's another solution that avoids the switch statement.
var map = new[] {
new { Value = "4100", StartsWith = false, Result="ABC" },
new { Value = "4101", StartsWith = false, Result="ABC" },
new { Value = "4102", StartsWith = false, Result="ABC" },
new { Value = "4200", StartsWith = false, Result="ABC" },
new { Value = "600A", StartsWith = false, Result="XWZ" },
new { Value = "3*", StartsWith = true, Result="123" },
new { Value = "03*", StartsWith = true, Result="123" },
};
var subarea = ... whatever ...;
var result = map.Where(e =>
{
if (e.StartsWith)
{
return subarea.StartsWith(e.Value);
}
else
{
return subarea == e.Value;
}
}
)
.Select(e => e.Result)
.FirstOrDefault() ?? "ABCXZ123";
The order in the array map determines the priority, so that, for example, you can have an exact match on, say, "3*11", as well as a StartsWith match on "3*", e.g.:
var map = new[] {
new { Value = "3*11", StartsWith = false, Result="ABC" },
new { Value = "4100", StartsWith = false, Result="ABC" },
new { Value = "4101", StartsWith = false, Result="ABC" },
new { Value = "4102", StartsWith = false, Result="ABC" },
new { Value = "4200", StartsWith = false, Result="ABC" },
new { Value = "600A", StartsWith = false, Result="XWZ" },
new { Value = "3*", StartsWith = true, Result="123" },
new { Value = "03*", StartsWith = true, Result="123" },
};
With LINQ, the nice answer by #seriesOne can be "simplified" a bit by replacing the foreach and return statements with:
// using System.Linq;
// Look for a match...
var result = cases
.Where(c => c.Item3(subArea, c.Item1))
.FirstOrDefault();
// Return the match or the default.
return result == null ? "ABCXYZ123" : result.Item2;

Syntax to execute code block inside Linq query?

Here's some code that (obviously) doesn't compile:
var q = from x in myAnonymousTypeCollection
select new {
x.ID,
CalcField = {
switch(x.SomeField) {
case 1:
return Math.Sqrt(x.Field1);
case 2:
return Math.Pow(x.Field2, 2);
default:
return x.Field3;
}
}
};
You get the picture; I'm trying to calculate CalcField in a completely different way, depending on what the value of SomeField is. I can't use a Func<> (or can I?), because the input type is anonymous. So what's the right syntax to get this to work?
First off, I usually prefer the method chain syntax over the query syntax for Linq. With that you can do this easily.
var q = myAnonymousTypeCollection
.Select(x =>
{
object calcField;
switch(x.SomeField)
{
case 1:
calcField = Math.Sqrt(x.Field1);
case 2:
calcField = Math.Pow(x.Field2, 2);
default:
calcField = x.Field3;
return new
{
x.ID,
CalcField = calcField
};
});
Without using method chains, you need either a method or an Func. Let's assume a Func
//replace these with actual types if you can.
Func<dynamic, dynamic> calculateField =
x =>
{
switch(x.SomeField) {
case 1:
return Math.Sqrt(x.Field1);
case 2:
return Math.Pow(x.Field2, 2);
default:
return x.Field3;
}
var q = from x in myAnonymousTypeCollection
select new { x.Id, CalcField = calculateField(x) };
Note: I didn't write this in an IDE, so please excuse any simple errors.
Here is the MSDN for dynamic. However, I have found that once you need to start passing anonymous types around, it is best to make an actual class.
You could wrap your anonymous function as a (self-executing) Func<> delegate. This assumes you know the return type.
var q = from x in myAnonymousTypeCollection
select new {
ID = x.ID,
CalcField = new Func<double>( () => {
switch(x.SomeField) {
case 1:
return Math.Sqrt(x.Field1);
case 2:
return Math.Pow(x.Field2, 2);
default:
return x.Field3;
}
} )()
};
You could quite easily move the switch logic out into another function like so:
private static T GetReturnValue<T>(myClass x)
{
switch (x)
{
case 1:
return Math.Sqrt(x.Field1);
break;
case 2:
return Math.Pow(x.Field2,
2);
break;
default:
return x.Field3;
break;
}
}
And then you just need to pass your object to that function to get back the value you want:
var q = from x in myAnonymousTypeCollection
select new
{
ID = x.ID,
CalcField = GetReturnValue(x)
};

Categories