switch() statement with string.contains() as parameter - c#

I'm somewhat new to C# and I'm trying to create a switch method that returns an ID (int) corresponding to the given filename.
For example:
var fileName = "file-example_MAP_COPY.xml";
var fileTypeId = GetFileTypeId(fileName); // Returns 3310
With the GetFileTypeId method looking something like this:
private GetFileTypeId(string fileName)
{
switch(string.Contains(fileName))
{
case ".xsd":
return 3010;
case "_Gui.xml":
return 3120;
case ".xml":
return 3300;
case "_MAP_COPY.xml":
return 3310;
...
}
}
I cannot trim the actual filename off and only keep the extension since the filename could contain underscores. A file with the name "example_1_MAP_COPY.xml" would be trimmed to "_1_MAP_COPY.xml" if trimmed at first underscore, resulting in a faulty file extension.
An if statement would work here, but since I have 18 different cases I'd like to find another solution than to write 18 if statements.
Is there some way I can go about to do this, either with a switch statement or a dictionary perhaps?

In current C#, you can do:
switch(filename) {
case string s when s.Contains(".xsd"): // or EndsWith, etc
...
}
I'm not saying that's the best approach or that it adds anything over if/else if, but... it works.
There's not much to choose between 18 complex case statements vs 18 if statements; except the if approach doesn't require you to add break; everywhere, and doesn't leak variable declarations between cases.
Personally, I'd use if/else if - or a static array of match/result pairs:
static readonly (string Match, int Result)[] MatchResults = new[] {
(".xsd", 3010),
("_Gui.xml", 3120),
// ...
};
...
foreach(var pair in MatchResults) {
if(filename.Contains(pair.Match)) return pair.Result;
}

You can use a switch indeed, but it seems you are only using the end of the string, so you could use another list type to save the patterns and their outcome:
var l = new []
{ new { Pattern = ".xsd", Value = 3010 }
, new { Pattern = "_MAP_COPY.xml", Value = 3310 }
};
foreach (var p in l)
{
if (filename.EndsWith(p.Pattern))
{
return p.Value;
}
}
// not found

You can use a switch indeed, but it seems you are only using the end of the string, so you could use a dictonary I think is simplier:
private readonly Dictionary<string, int> _ids = new Dictionary<string, int>
{
{".xsd", 3010},
{".xml", 3300},
{"_Gui.xml", 3120},
{"_MAP_COPY.xml", 3310}
};
private int GetFileTypeId(string fileName)
{
var element = _ids.FirstOrDefault(_ => fileName.Contains(_.Key));
return element.Value;
}

Related

Replace switch statements for child class type

There is a method that accepts 2 parameters:
int selectedClass;
int selectedFunction;
Next, there goes 2 switch statements. First of all, it determines child class type using enum:
ParentClass p;
switch(selectedClass){
case (int)ClassTypes.A:
p = new classA();
break;
case (int)ClassTypes.B:
p = new classB();
break;
case (int)ClassTypes.C:
p = new classC();
break;
}
And it goes on an on for like 50 more statements. Furthermore, there is another switch statement that determines a function:
string result;
switch(selectedFunction){
case (int)FunctionTypes.Func1:
result = p.function1();
break;
case (int)FunctionTypes.Func2:
result = p.function2();
break;
case (int)FunctionTypes.Func3:
result = p.function3();
break;
}
I did use the search, there are a lot of examples of improving the second switch statement, but not the first one. The 1st question is: how do we determine both child class and function with no switch statements?
2nd: In js I would do something like that:
functionsArray[selectedClass][selectedFunction]();
Is it possible to implement similar kind of mapping in c#?
Update #1:
I have replaced the 1st switch with the following code:
public static Dictionary<ClassTypes, Type> typeDict = new Dictionary<ClassTypes, Type>()
{
{ ClassTypes.A , typeof(classA) },
{ ClassTypes.B , typeof(classB) },
{ ClassTypes.C , typeof(classC) }
};
ParentClass p = (ParentClass)Activator.CreateInstance(typeDict[selectedClass]);
I can't say I understand the logic that lead you to pick this strange design, but I can think of at least two ways to improve it, providing all the functions you are calling are implemented in the base class (and overriden when needed in the derived classes, of course).
Both solutions are only relevant in case all classes provides a parameter-less constructor and parameter-less functions, and executing the functions does not require further initialization:
The first solution would require you to change the method signature, and force the calling method to know the types of the classes so you might not be able to implement it but it involves far less code.
ExecuteMethod<TClass>(Func<TClass, string> func) where T: BaseClass, new()
(
return func(new T());
)
And you call it like this:
var result = ExecuteMethod<ClassA>(a => a.Function1);
The second solution
This might be more suitable for your needs:
You will need to create two dictionaries and populate them, like this:
private Dictionary<int, Func<ParentClass>> constructors = new Dictionary<int, Func<ParentClass>>()
{
{1, () => new ClassA()},
{2, () => new ClassB()}
// more of the same
};
private Dictionary<int, Func<ParentClass, string>> methods = new Dictionary<int, Func<ParentClass, string>>()
{
{1, i => i.function1},
{2, i => i.function2}
// more of the same
};
Then your method can still take two ints and return a string:
string DoSomething(int classType, int function)
{
var instance = constructors[classType].Invoke();
return methods[function].Invoke(instance);
}
Please note that the code is written directly here and not tested, so I might have missed a thing or two, but this is the general idea.
I did some changes to your original code, I changed the input parameters to strings, assuming you can directly take names as input. And then instantiating using the first method and invoking using the second. I have added an overload if you want to continue to use the Enum.
string selectedClass;
string selectedFunction;
public object GetClassInstanceFromName(string name)
{
object type = Type.GetType($"{this.GetType().Namespace}.{name}";
return Activator.CreateInstance((Type)type);
}
public string InVokefunctionByName(object instance,string methName)
{
return instance.GetType().GetMethod(methName).Invoke(instance, null) as string;
}
//Overload if you want to continue to use your enum
public object GetClassInstanceFromName(ClassTypes name)
{
return
Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName,
"class" +name.ToString());
}
private void Test()
{
object a = GetClassInstanceFromName(selectedClass);
Console.WriteLine(InVokefunctionByName(a, selectedFunction));
Console.ReadKey();
}
Also switching to a better design will always be recommended way.

Alternative for switch that only calls methods based on cases values

Is there a possible way to write the next switch in some shorter, readable code?
switch (SomeValue)
{
case "001": return DoMethod1(); break;
case "002": return DoMethod2(); break;
//etc..
}
I was thinking in some way like
Dictionary<string, Func<int>> MethodsByValue = new Dictionary<string, Func<int>>()
{
{ "001", DoMethod1 },
{ "002", DoMethod2 },
}
and call this by doing
return MethodsByValue[SomeValue]();
But is this even possible? Or am I thinking way to far out of the box. I couldn't find anyting like this but then again, I don't know the keywords for this if it is possible.
EDIT: To answer the request of Lasse V. Karlsen:
This is how the code is in my project. Changed names at some places cause original names doesn't matter cause it is in my mother language.
public string GetRecord420(Dictionary<DataClass, object> dictionaryName)
{
// some code here
}
public string GetRecord421(Dictionary<DataClass, object> dictionaryName)
{
// some code here
}
//(Temperary) solution with the switch statement in a wrapper:
public string GetRecordByString(string s, Dictionary<DataClass, object> dictionaryName)
{
switch (s)
{
case "320": return GetRecord420(dictionaryName);
case "321": return GetRecord421(dictionaryName);
default: return String.Empty;
}
}
//How I hoped it could be, with a mapping dictionary.
public Dictionary<string, Func<string, Dictionary<DataClass, object>>> MethodByString =
new Dictionary<string, Func<string, Dictionary<DataClass, object>>>()
{
{ "320", GetRecord420 },
{ "321", GetRecord421 },
}
DataClass is an Entity class, which stores some column data (column name, column type, etc.).
I tried the dictionary part, but it gives me the error: Cannot convert from method group to System.Func<...>.
Changing to () => GetRecord420 gives me the error: Cannot convert lambda to delegate type System.Func<...> because some of the return types in the block are not implicitly convertible to the delegate return type.
There must be an error with your method definitions,
class Program
{
static void Main()
{
var methods = new Dictionary<string, Func<int>>
{
{ "001", DoMethod1 }
};
}
static int DoMethod1()
{
return 1;
}
}
is perfectly valid syntax.
but, this is not better than switch for 1 compelling and 1 subjective reason.
If you are comparing against constants or literals then you should use switch. This enables the compiler to peform compile time optimizations without additional analysis.
More subjectively, the dictionary/lookup approach is no shorter and I find it harder to read. However, it would be useful in situations where your comparison terms vary at runtime.
If you want avoid rewriting the switch factor it into a function. Say,
Func<int> MethodsByValue(string value)
{
switch(value)
{
case "001":
return DoMethod1;
default:
return DoMethod2;
}
}
Either way,
Rather than using some arbritary strings to enumerate your methods, why not use an enum? Then you will get additional performance and readability benefits.
In general a good alternative for switch would be using State Design Pattern other good alternative is Strategy Pattern. It will make your code more extensible and is more object oriented approach.
You can shorten what you have simply by removing the redundant breaks, and if it happens that two cases should call the same method then you can fall through:
switch (SomeValue) {
case "001": return DoMethod1();
case "002": return DoMethod2();
case "003":
case "004": return DoMethod34();
//etc..
}
As for your pseudo suggestion, and the other answer advocating it, I don't see how this is any short or more concise. However, in usage is could reduce code and be clear, such as, briefly:
Func<int> GetMethod(string key) {
return MethodsByValue[key];
}
Func<int> method = GetMethod("001");
method();
I use a Mapping extension for that. This way yoy can use the following syntax:
return SomeValue
.Map("001", DoMethod1)
.Map("002", DoMethod2)
//etc
This makes it also possible to do this:
return SomeValue
.Map(1, DoMethod1)
.Map(2, DoMethod2)
.Map(x => x < 0, DoMethod3)
.Map(x => x > 5 && x < 10, DoMethod4)
.Else(4); // a simple value of a method

Putting a field to the next empty array index

How can I put a string to the next empty index in a string array? I want to use a foreach loop and see if all the boolean strings are valid bools, and then put the keys of the invalid boolean strings to an array
string[] invalidKeys;
foreach (string key in ConfigurationManager.AppSettings)
{
string value = ConfigurationManager.AppSettings[key];
if (IsValidBooleanString(value) == false)
{
//Add 'key' to next empty index in the array 'invalidKeys'.
}
return invalidKeys;
You haven't initialized or specified the length of the array. You need to specify the length for the Array to create one, but in your case you don't know that info in advance
So you can use a List instead
var invalidKeys = new List<String>();
foreach (string key in ConfigurationManager.AppSettings.Keys)
{
string value = ConfigurationManager.AppSettings[key];
if (IsValidBooleanString(value) == false)
{
//Add 'key' to next empty index in the array 'invalidKeys'.
invalidKeys.Add(key);
}
return invalidKeys;
Also noticed your foreach should be on ConfigurationManager.AppSettings.Keys not ConfigurationManager.AppSettings
Other way of doing this will be
var invalidKeys =
ConfigurationManager.AppSettings.Keys
.Where(k => IsValidBooleanString(ConfigurationManager.AppSettings[k]) == false)
.ToArray();
If you really want to become a C# programmer:
return (from string key in ConfigurationManager.AppSettings
where !IsValidBooleanString(ConfigurationManager.AppSettings[key])
select key).ToList();
Or ToArray() or whatever.
The functionality of IsValidBooleanString() may be useful elsewhere. I would recommend placing it in an extension class:
public static class StringExtender
{
static readonly string[] validBooleanStrings = { "True", "False", "Yes", "No" };
public static bool IsValidBooleanString(this string value)
{
return ValidBooleanStrings.Contains(value, StringComparer.OrdinalIgnoreCase);
}
}
Some will say it's overkill to create an extension class for a single method, but it's really not and it makes your code modular and reusable. Not to mention you can add more extension methods here as you encounter similar scenarios.
Now all string objects have a .IsValidBooleanString() method attached to them and automatically pass themselves (this) to it. MSDN: Extension Methods (C#)
For retreiving a list of all setting keys with invalid boolean string values, I would use a LINQ query:
var settings = ConfigurationManager.AppSettings;
// Gets all the keys for values that are invalid boolean strings.
var invalidKeys = from key in settings.Keys
where !settings[key].IsValidBooleanString()
select key;
// If you want a list...
var invalidKeyList = invalidKeys.ToList<string>();
// If you want an array...
var invalidKeyArray = invalidKeys.ToArray<string>();
LINQ queries return IEnumerable<> values based on what you give it. Since I am selecting on a string type (key), it knows to give me back an IEnumerable<string>. You can then create a List<string> or string[] from the results if you wish.

Is there a way to shorten the condition if it always compares to the same thing?

I have always found it annoying when I need to write a condition that compares the same item over and over again since I would have the type the item so many times:
string x = textBox1.Text;
if (x == "apple" || x == "orange" || x == "banana" ...)
...
I want something like this (but of course this is not correct syntax):
if (x == "apple" || "orange" || "banana" ...)
Is there a solution other than using an array of the strings?
Your condition says: I'm true if I match any of the predefined values. In other words if I'm an element of a predefined set which is semantically the Contains method:
if (new [] { "apple", "orange", "banana" }.Contains(x))
{
}
Using an array provides much more flexibility in the future. You can extract it out, reuse it, store it, chache it etc. I always use "arrays and loops" when I have to deal with more than 2 known values.
Note: As Scott Chamberlain pointed out in the comments with using HashSet<T>.Contains greatly improves the performace:
var values = new HashSet<string> { "apple", "banana", "orange" };
if (values.Contains(x))
{
}
What about an extension method?
public static class Extensions
{
public static bool IsOneOf<T>(this T input, params T[] possibilites)
{
bool result = possibilites.Contains(input);
return result;
}
}
You could then rewrite your code to look like this:
string input = textBox1.Text;
if(input.IsOneOf("apple", "orange", "banana"))
{
// ....
}
Your best bet (in terms of performance) is to use HashSet
static HashSet<string> Fruits = new HashSet<string> {"apple", "banana", "orange"};
string x = textBox1.Text;
if( Fruits.Contains( x)) {
Once you get beyond three or so possibilities in the if condition, a HashSet will run faster than straight comparison.
You can move duplicated code to the method, which also will explain why this code is duplicated - because it verifies whether something is fruit. It will increase readability and maintainability of your code. Also you will be able to refactor this logic (e.g. turning it into switch statement):
private bool IsFruit(string name)
{
switch(name)
{
case "apple":
case "orange":
...
case "banana":
return true;
default:
return false;
}
}
Usage:
string x = textBox1.Text;
if(IsFruit(x))
...
UPDATE: Better do not use such complex conditions - it's really hard to understand sometimes. You can use Introduce Explaining Variable or Extract Method (as above) refactorings to make your code more clear.
use a switch statement with like so
switch (x)
{
case "apple":
case "orange":
case "banana":
//code
break;
}
You could try a switch statement:
switch (x)
{
case "apple":
case "orange":
case "banana":
//...
break;
}
You can use a switch statement:
switch(x)
{
case "apple":
case "orange":
case "banana":
// "if" code goes here
break;
default:
// "else" code goes here
break;
}
linq solution
var strarray = new string[]
{
"apple",
"orange",
"banana"
};
bool a = strarray.Any( x=> x == textBox1.Text);
if(a)
//CODE
else
//code
try switch case like this
Switch(value)
{
case "apple":
case "orange":
case "banana":....
//code you want
break;
}
I like the extension method solution and have used it before. Here are the methods I have available in my "CommonUtils" library:
public static bool IsIn<T>(this T toFind, IEnumerable<T> collection)
{
return collection.Contains(toFind);
}
public static bool IsIn<T>(this T toFind, ICollection<T> collection)
{
return collection.Contains(toFind);
}
public static bool IsIn<T>(this T toFind, params T[] items)
{
return toFind.IsIn(items.AsEnumerable());
}
Between these three, you can pretty much use any collection, and you can also specify the items as a parameter list.
You could do something like this:
string it = "apple,orange,banana";
if(it.Contains(x))
{
//do work
}
Even simpler:
if("apple,orange,banana".Contains(x))
{
//do work
}

Another Linqification question

Say I have a List of objects, and the object has a string property. I want to get a single comma-separated list of the value of each string property of each object in the list.
Here's 1 way to do it (sans linq)
StringBuilder result = new StringBuilder()
foreach(myObject obj in myList)
{
result.Append(obj.TheString);
result.Append(", ");
}
// then trim the trailing ", " and call ToString() on result, etc, etc...
Here's my first shot at linqification. Is there a better way?
string result = string.Join(", ", myList.Select(myObj => myObj.TheString).ToArray());
That's one line of code, but it doesn't look very efficient to me -- iterate the list just to build an array, just to iterate the array and build a string... whew!
Is there a better way?
If you want efficient, use Enumerable.Aggregate with StringBuilder:
string result = myList.Aggregate(new StringBuilder(),
(sb, o) => sb.Append(o.TheString).Append(", "))
.ToString();
The original problem is that String.Join wants an array. In .NET 4, there will be an overload that takes IEnumerable<string> (and I expect it will be implemented like above).
I like this extension method for joining strings. It's basically the same technique you are using, but wrapped in an extension method. I wouldn't recommend it for large sets since efficiency was not the goal. The benefit to me is expressiveness (very linqy) and convenient for small sets:
[Test]
public void Should_make_comma_delimited_list()
{
var colors = new List<HasColor>
{
new HasColor { Color = "red" },
new HasColor { Color = "green" },
new HasColor { Color = "blue" }
};
var result = colors.Implode(x => x.Color, ", ");
Assert.That(result, Is.EqualTo("red, green, blue"));
}
public class HasColor
{
public string Color { get; set; }
}
public static class LinqExtensions
{
public static string Implode<T>(this IEnumerable<T> list, Func<T, string> func, string separator)
{
return string.Join(separator, list.Select(func).ToArray());
}
}
Use string.Join, it's good enough.
Optimize when profiler will tell you to do so.
Readability of the version with StringBuilder is poor and you are still getting your trailing comma.
Here's another way (result is a StringBuilder):
myList.ForEach(
myObj =>
{
if (result.Length != 0)
result.Append(",");
result.Append(myObj.TheString);
}
);

Categories