need to create a summary of a large switch statement in C# - c#

Alright, i dont know how to explain it well.. but i have a switch statement,
string mystring = "hello";
switch(mystring)
{
case "hello":
break;
case "goodbye":
break;
case "example":
break;
}
of course this is an example, and in the real situation, there will be different things happening for each case.
ok, hope you get the point, now, doing this manually is impossible, because of the sheer number of different case's. i need to respectively create a list, of all the cases, so for instance.. for the above switch statement, i would need
string[] list = { "hello", "goodbye", "example" };
maybe could be done with a foreach some how i dont know, any help would be greatly appreciated.
also, any working codes provided would be awesome!
edit:
people are asking for more detail, so here is how it works.
the user of the program, inputs a series of strings.
based on the string(s) they entered, it will do a few if's and else if's and throw back the new strings basically. i need to be able to be able to create a list, through the program, of all the options available to use. and i cant just make a list and hard code it in, because im always adding more case's to the statement, and i cant be going back and keeping a list up to date.

FOR VISUAL STUDIO:
if mystring is an enum instead of a string, in visual studio, if you type "switch" [TAB] "mystring" [ENTER] it'll build the long switch for you with all the cases.

It depends on how clever you want to get... You could create a custom attribute that attaches to a method with the string that method should handle. Then, instead of a switch statement, you would just find the attribute with your desired value and execute it.
using System;
using System.Reflection;
namespace ConsoleApplication1 {
[AttributeUsage(AttributeTargets.Method)]
internal class ProvidesAttribute : Attribute {
private String[] _strings;
public ProvidesAttribute(params String[] strings) {
_strings = strings;
}
public bool Contains(String str) {
foreach (String test in _strings) {
if (test.Equals(str)) {
return true;
}
}
return false;
}
}
internal class Program {
[Provides("hello", "goodbye")]
public void HandleSomeStuff(String str) {
Console.WriteLine("some stuff: {0}", str);
}
[Provides("this")]
public void HandleMoreStuff(String str) {
Console.WriteLine("more stuff: {0}", str);
}
public void HandleString(String str) {
// we could loop through each Type in the assembly here instead of just looking at the
// methods of Program; this would allow us to push our "providers" out to other classes
MethodInfo[] methods = typeof(Program).GetMethods();
foreach (MethodInfo method in methods) {
Attribute attr = Attribute.GetCustomAttribute(method, typeof(ProvidesAttribute));
ProvidesAttribute prov = attr as ProvidesAttribute;
if ((prov != null) && (prov.Contains(str))) {
method.Invoke(this, new Object[] { str } );
break; // removing this enables multiple "providers"
}
}
}
internal static void Main(String[] args) {
Program prog = new Program();
foreach (String str in args) {
prog.HandleString(str);
}
}
}
}
Once you have the framework, you wouldn't need to alter the HandleString() code, just add the methods you want to take care of and set the Provides attribute on them. If you wanted to extend the idea a little further, you could create multiple classes to handle a wide variety of strings, then loop through each type in your assembly looking for the Provides attribute.
EDIT this has the added benefit that you can define multiple methods that act on the same string (by removing the break in the loop logic).

I'm note sure what you are trying to do, but you might be able to use a dictionary.
Dictionary<string, int> lookupTable = new Dictionary<string, int>();
lookupTable.Add("hello", 1);
lookupTable.Add("goodbye", 2);
lookupTable.Add("example", 3);
int output = lookupTable["hello"];
You wouldn't need to have code to add each individual entry. You could read in the keys and values from a file, loop though them and populate the dictionary.
If you explain more about what you are trying to do, we could give you more specific advice.

By proper refactoring (your hypothetical example) you can make sure that out of your sheer number of cases, there will be a lot of them that can call the same sub routine with their string parameter.
In many of these scenarios, you may not even need a huge switch statement, but just parameterize one sub routine that can handle them.
Without a concrete example of what you want to do in the case statements, it is hard to come up with a concrete answer.

You appear to be trying to extract "command strings" from your code, so that you can automatically update the list of available commands in your user documentation. I think this will not gain you much, as you will still need to manually document what each command does.
That being said, the following powershell command will extract the data you want from test.cs:
type test.cs|select-string 'case "(.*)"'|foreach {$_.Matches[0].Groups[1].Value}

Switch statements evaluate on constants, so the case statements won't work with variables. Perhaps you should consider using a Dictionary<> and branching based on that. But without any more insight into the problem you're solving, there's little point in saying anything more.

Create an abstract class, call it something like StringHandler. Give it 2 abstract methods, 1 to check whether the handler can handle the string, then the other to do the processing. Something like:
public abstract class StringHandler
{
public abstract bool CanProcess(string input);
public abstract void Process();
}
public class HelloStringHandler : StringHandler
{
public override bool CanProcess(string input)
{
return input.Equals("hello");
}
public override void Process()
{
Console.WriteLine("HELLO WORLD");
}
}
Then in your main class you can do a simple loop with a list of all known handlers, like
List<StringHandler> handlers = new List<StringHandler>();
handlers.Add(new HelloStringHandler());
string myString = "hello";
foreach (StringHandler handler in handlers)
{
if (handler.CanProcess(myString))
{
handler.Process();
break;
}
}
All this can be optimised/improved obviously, but I hope you get the picture?

I am very rusty at c#, but this was a fun little exercise. The following code is not very clean, but will do what you asked. You will want to add more checks, use the variables better and add more logic, but this should help you get going in the right direction.
var newfile = System.IO.File.CreateText("newcode.txt");
newfile.Write("string[] list = { ");
using (var file = System.IO.File.OpenText("code.txt"))
{
bool bFirst = true;
while (!file.EndOfStream)
{
String line = file.ReadLine();
if (line.Contains("case ") && line.EndsWith(":"))
{
line = line.Replace("case", " ");
line = line.Replace(":", " ");
line = line.Trim();
if (bFirst == false)
{
newfile.Write(", ");
}
bFirst = false;
newfile.Write(line);
}
}
}
newfile.WriteLine(" };");
newfile.Close();
Good luck!

Inspired by #Jheddings answer, I came up with this. Maybe it's over the top, but at least I had fun figuring it out:
Main benefits over jheddings solution:
Uses extension methods, no utility class instance needed.
Reflection lookup of all candidate methods is done only once, right before the first string is evaluated. Afterwards, it is a simple lookup and invoke.
Even simpler usage
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace StringSwitcher
{
class Program
{
static void Main(string[] args)
{
"noAction".Execute(); //No action, since no corresponding method defined
"Hello".Execute(); //Calls Greet method
"world".Execute(); //Calls Shout method
"example".Execute(); //Calls Shout method
Console.ReadKey();
}
//Handles only one keyword
[Keywords("Hello")]
static public void Greet(string s)
{
Console.WriteLine(s + " world!");
}
//Handles multiple keywords
[Keywords("world", "example")]
static public void Shout(string s)
{
Console.WriteLine(s + "!!");
}
}
internal static class ActionBrokerExtensions
{
static Dictionary<string, MethodInfo> actions;
static ActionBrokerExtensions()
{
//Initialize lookup mechanism once upon first Execute() call
actions = new Dictionary<string, MethodInfo>();
//Find out which class is using this extension
Type type = new StackTrace(2).GetFrame(0).GetMethod().DeclaringType;
//Get all methods with proper attribute and signature
var methods = type.GetMethods().Where(
method => Attribute.GetCustomAttribute(method, typeof(KeywordsAttribute)) is KeywordsAttribute &&
method.GetParameters().Length == 1 &&
method.GetParameters()[0].ParameterType.Equals(typeof(string)));
//Fill the dictionary
foreach (var m in methods)
{
var att = (Attribute.GetCustomAttribute(m, typeof(KeywordsAttribute)) as KeywordsAttribute);
foreach (string str in att.Keywords)
{
actions.Add(str, m);
}
}
}
public static void Execute(this string input)
{
//Invoke method registered with keyword
MethodInfo mi;
if (actions.TryGetValue(input, out mi))
{
mi.Invoke(null, new[] { input });
}
}
}
[AttributeUsage(AttributeTargets.Method)]
internal class KeywordsAttribute : Attribute
{
private ICollection<string> keywords;
public KeywordsAttribute(params String[] strings)
{
keywords = new List<string>(strings);
}
public ICollection<string> Keywords
{
get { return keywords; }
}
}
}
Apologies for any strange rendering, for some reason the syntax highlighting chokes on the code :-(

Related

C# - Using attributes and reflection in a command tool [duplicate]

This question already has answers here:
How enumerate all classes with custom class attribute?
(8 answers)
Closed 3 years ago.
I'm working on a command tool in C#, although not for a terminal command-line. I have read the documentation on reflection and attributes but I'm not sure exactly what the "right" way to go about this is.
The problem isn't very complicated, but it needs to be easily extended. I need to just have Commands that are picked up and loaded in where their triggering strings are checked and if they match, methods are called. How I went about it just as a proof-of-concept was:
[System.AttributeUsage(System.AttributeTargets.Class)]
public class CommandAttribute : Attribute
{
public string Name { get; private set; } //e.g Help
public string TriggerString { get; private set; } //e.g. help, but generally think ls, pwd, etc
public CommandAttribute(string name, string triggerStrings)
{
this.Name = name;
this.TriggerString = triggerString;
}
}
Now, I decorated the class and it will implement methods from an interface. Eventually there will be many commands and my idea is to make it easy for someone with minimal programming experience to jump in and make a command.
using Foo.Commands.Attributes;
using Foo.Infrastructure;
namespace Foo.Commands
{
[Command("Help", "help")]
public class Help : IBotCommand
{
// as an example, if the message's contents match up with this command's triggerstring
public async Task ExecuteAction()
}
}
This gets injected into the console app where it will load the commands and get passed messages
public interface ICommandHandler
{
Task LoadCommands();
Task CheckMessageForCommands();
}
Then, everything with a matching attribute will get loaded in and when a message is received, it will check its contents against all CommandAttribute decorated classes' triggering strings, and if it matches, call the method ExecuteAction on that command class.
What I've seen/tried: I understand how to use reflection to get custom attribute data, however I'm confused as to getting the methods and calling them, and how all of this should be configured to be fairly performant with reflection being used. I see CLI tools and chat bots that use a similar method, I just cannot peek into their handlers to see how these get loaded in and I can't find a resource that explains how to go about accessing the methods of these classes. Attributes may not be the right answer here but I'm not sure how else to go about it.
Really, my main question is:
How do I setup The CommandHandler to load all of the attribute-decorated classes and call their methods, and how they should be instantiated within it. I know the second piece may be a bit more subjective but would newing them up be improper? Should they somehow be added to DI?
My solution ended up just using the Activator and lists. I still need to tweak this for performance and run more extensive stress tests, but here is my quick code for it:
// for reference: DiscordCommandAttribute is in Foo.Commands library where all the commands are, so for now it's the target as I removed the base class
// IDiscordCommand has every method needed, so casting it as that means down the line I can call my methods off of it. The base class was just for some reflection logic I was testing and was removed, so it's gone
public void LoadCommands() // called in ctor
{
var commands =
from t in typeof(DiscordCommandAttribute).Assembly.GetTypes()
let attribute = t.GetCustomAttribute(typeof(DiscordCommandAttribute), true)
where attribute != null
select new { Type = t, Attribute = attribute };
foreach (var obj in commands)
{
_commandInstances.Add((IDiscordCommand)Activator.CreateInstance(obj.Type));
_commandAttributes.Add(obj.Attribute as DiscordCommandAttribute);
}
}
There is probably a more sugary way to handle adding the objects to the lists, and some other data structure besides Lists might be more suitable, I'm just not sure if HashSet is right because it's not a direct Equals call. Eventually I will genericize the interface for this class and hide all of this logic in a base class. Still a lot of work to do.
Currently, just putting a stopwatch start before calling LoadCommands shows that the entire load takes 4ms. This is with 3 classes and a pretty anemic attribute, but I'm not too worried about the scale as I want any overhead on launch and not during command handling.
Using some code I wrote for this answer, you can find all types that implement an interface, e.g. IBotCommand, and then retrieve the custom attribute:
public static class TypeExt {
public static bool IsBuiltin(this Type aType) => new[] { "/dotnet/shared/microsoft", "/windows/microsoft.net" }.Any(p => aType.Assembly.CodeBase.ToLowerInvariant().Contains(p));
static Dictionary<Type, HashSet<Type>> FoundTypes = null;
static List<Type> LoadableTypes = null;
public static void RefreshLoadableTypes() {
LoadableTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetLoadableTypes()).ToList();
FoundTypes = new Dictionary<Type, HashSet<Type>>();
}
public static IEnumerable<Type> ImplementingTypes(this Type interfaceType, bool includeAbstractClasses = false, bool includeStructs = false, bool includeSystemTypes = false, bool includeInterfaces = false) {
if (FoundTypes != null && FoundTypes.TryGetValue(interfaceType, out var ft))
return ft;
else {
if (LoadableTypes == null)
RefreshLoadableTypes();
var ans = LoadableTypes
.Where(aType => (includeAbstractClasses || !aType.IsAbstract) &&
(includeInterfaces ? aType != interfaceType : !aType.IsInterface) &&
(includeStructs || !aType.IsValueType) &&
(includeSystemTypes || !aType.IsBuiltin()) &&
interfaceType.IsAssignableFrom(aType) &&
aType.GetInterfaces().Contains(interfaceType))
.ToHashSet();
FoundTypes[interfaceType] = ans;
return ans;
}
}
}
public static class AssemblyExt {
//https://stackoverflow.com/a/29379834/2557128
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) {
if (assembly == null)
throw new ArgumentNullException("assembly");
try {
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null);
}
}
}
Note: If you create types at runtime, you will need to run RefreshLoadableTypes to ensure they get returned.
If you are concerned about IBotCommand implementors existing without the CommandAttribute, you can filter the ImplementingTypes, otherwise:
var botCommands = typeof(IBotCommand)
.ImplementingTypes()
.Select(t => new { Type = t, attrib = t.GetTypeInfo().GetCustomAttribute<CommandAttribute>(false) })
.Select(ta => new {
ta.Type,
TriggerString = ta.attrib.TriggerString
})
.ToDictionary(tct => tct.TriggerString, tct => tct.Type);
With an extension method for your command Types:
public static class CmdTypeExt {
public static Task ExecuteAction(this Type commandType) {
var cmdObj = (IBotCommand)Activator.CreateInstance(commandType);
return cmdObj.ExecuteAction();
}
}
You can use the Dictionary like:
var cmdString = Console.ReadLine();
if (botCommands.TryGetValue(cmdString, out var cmdType))
await cmdType.ExecuteAction();
Overall, I might suggest having a method attribute and having static methods in static classes for commands, so multiple (related?) commands can be bundled in a single class.
PS My command interpreters have help associates with each command, and categories to group commands, so perhaps some more attribute parameters and/or another IBotCommand method to return a help string.

Handling Nested Property Values

What is the elegant solution to access nested property values?
Example:
In some cases it could look as follows:
public void someFunction()
{
this.Device.ResponseHandler.Process(this.Device.TcpClient.responseMessage, this.Device.TcpClient.responseType)
}
My solution was to copy objects, just to shorten the names afterwards.
public void someFuntion()
{
// Just for shorten the access name afterwards
ResponseHandler responseHandler = this.Device.RepsonseHandler;
TcpClient tcpClient = this.Device.TcpClient;
responseHandler.Process(tcpClient.responseMessage, tcpClient.responseType);
}
It is mostly opinion based, but there are generally two ways:
The one you are using.
List every argument in new line:
public void someFunction()
{
this.Device.ResponseHandler.Process(
this.Device.TcpClient.responseMessage,
this.Device.TcpClient.responseType
);
}
IMO both are equally readable and in second approach you don't need another variables :)
You could add a usings at the top if you don't like long names:
using ResponseHandler = this.Device.ResponseHandler;
using TcpClient = this.Device.TcpClient;
public void someFunction()
{
ResponseHandler.Process(TcpClient.responseMessage, TcpClient.responseType);
}

How Call Method<T> Generic

I have a problem on having called a generic method, I have searched very much and do not find a solution, this one is my mistake.
And I do not have knowledge of I am doing badly,
The type arguments for method 'FormGasolineUserControl.loadList<T>(list<T>,string)' cannot be inferred from the usage. try specifying the type arguments expliciy.
this is my code:
This chunk of code where I obtain the error
private void loadDataForm()
{
try
{
DateTime dateNow = DateTime.Now;
// Call Services WPF
var QueryBD = services.LoadDataFormGasoline(1, (int)ETax.Gasoline);
if (QueryBD.Company != null)
{
day.Value = dateNow.Day.ToString();
month.Value = dateNow.Month.ToString();
year.Value = dateNow.Year.ToString();
anioGravable.Value = dateNow.Year.ToString();
peridoGravable.Value = PeriodoGravable(dateNow).ToString();
//Error call Method
loadList( QueryBD.QualityDeclarate, QualityDeclarate.Name.ToString());
loadList( QueryBD.TypeDeclarate, TypeDeclarate.Name.ToString());
}
}
catch (Exception)
{
throw;
}
}
And this one is the method:
public void loadlist<T>(List<T> lista,string nameControl)
{
try
{
switch (nameControl)
{
case "TypeDeclarate":
TypeDeclarate.Items.Add(new ListItem("Select..."));
foreach (var name in lista)
{
TypeDeclarate.Items.Add(new ListItem(name.ToString()));
}
break;
case "QualityDeclarate":
QualityDeclarate.Items.Add(new ListItem("Select..."));
foreach (var name in lista)
{
QualityDeclarate.Items.Add(new ListItem(name.ToString()));
}
break;
}
}
catch (Exception)
{
throw;
}
}
My principal aim is to be able to load the usercontrol dynamicamente by means of lists consulted in database.
help me plis...
This code could be improved in a great many ways.
It should not be generic.
It could be refactored into smaller methods that are more clear.
The naming conventions do not follow C# conventions.
It takes a list but only enumerates the elements
It really operates on sequences of strings.
The try-catch is useless.
Let's fix it.
private void AddItemsToCollection(IEnumerable<string> names, IList<ListItem> items)
{
items.Add(new ListItem("Select..."));
foreach (var name in names)
items.Add(new ListItem(name));
}
See how simple that is? Make simple methods that do one thing well. Now we use that helper to make other simple methods:
private void AddItemsToCollection(IEnumerable names, IList<ListItem> items)
{
AddItemsToCollection(names.Cast<object>().Select(n => n.ToString(), items);
}
Again, super simple. One line. Let's make more one-liners:
public void AddTypeDeclarateItems(IEnumerable names)
{
AddItemsToCollection(names, TypeDeclarate.Items);
}
SO EASY. Do it again.
public void AddQualityDeclarateItems(IEnumerable names)
{
AddItemsToCollection(names, QualityDeclarate.Items);
}
And now our method is simple:
public void AddItemsToCollection(IEnumerable names, string control)
{
switch (control)
{
case "TypeDeclarate":
AddTypeDeclarateItems(names);
break;
case "QualityDeclarate":
AddQualityDeclarateItems(names);
break;
}
}
Your code will get easier to understand, easier to make correct, easier to debug, if you simplify it so that every method does one thing.
Alternative solution: move the switch into a helper:
IList<ListItem> GetItems(string control)
{
switch (control)
{
case "TypeDeclarate":
return TypeDeclarate.Items;
case "QualityDeclarate":
return QualityDeclarate.Items;
}
throw new SomeException(...);
}
And now our method is:
public void AddItemsToCollection(IEnumerable names, string control)
{
AddItemsToCollection(names, GetItems(control));
}
Again, see what happens when you make every method do one thing? Every method gets really easy to understand, and highly likely to be correct.
This is the correct way to call this method:
...
loadlist<string>(QueryBD.myListOfStrings, "nameControl");
loadlist<int>(QueryBD.myListOfInts,"nameControl");
...
But, I guess that maybe the problem is inside the class that contains the method
void loadlist<T>(List<T> lista,string nameControl).
Aswer this question: What is T? I mean, in a class declaration we have many possibilities.
We can set T argument like a class: public MyClass<T> where T : class;
A struct: public MyClass<T> where T : struct; A class that has a public constructor public MyClass<T> where T : new(),...
See all possibilities: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters
So, let's imagine that T is a class that must implements an interface and you're passing a class that doesn't implement it, so you'll not have success.

How to store a list of Cases from Switch Statement?

In my program I have a listbox that when the user double clicks an object it looks to a switch statement to see what event should occur. As the list begins getting larger I'm curious if there is a way to avoid having to maintain the list of objects in 2 places (once in a list to Add to the listbox, and once in the switch statement.
Is there a way to index/read/store the various Cases of my switch statement, then add them as objects to my listbox?
Example: (doesn't work, just a theory)
Switch (n)
ForEach (Case c in Cases)
{
arrayCases.Add(c);
}
listbox.Items.AddRange(arrayCases);
EDIT:
Going on the Dictionary recommendations I now have:
public void SetDictionary()
{
//add entries to the dictionary
dict["cat"] = new Action(Cat);
dict["dog"] = new Action(Dog);
//add each dictionary entry to the listbox.
foreach (string key in dict.Keys)
{
listboxTest.Items.Add(key);
}
}
//when an item in the listbox is double clicked
private void listboxTest_DoubleClick(object sender, EventArgs e)
{
testrun(listboxCases.SelectedItem.ToString());
}
public void testrun(string n)
{
//this is supposed to receive the item that was double clicked in the listbox, and run it's corresponding action as defined in the dictionary.
var action = dict[n] as Action action();
}
I believe that my code above is mostly correct and that I'm understanding it, however the action line:
var action = dict[n] as Action action();
Shows an error stating 'action' is expecting a ';'. Is my logic here accurate? If so, why is the action call incorrect?
Dictionary<string, Action> is the way to avoid. Dictionary.Keys becomes ListBox.Items.
switch(n) becomes
var action = dict[n] as Action
action();
I suggest to move your operations into separate classes. Create a base class for your operations like the following one. I added a field for the form because you probably have to interact with your form. You can also pass in other objects if required.
internal abstract class Operation
{
protected readonly MyForm form = null;
protected Operation(MyForm form)
{
this.form = form;
}
public abstract String DisplayName { get; }
internal abstract void Execute();
}
Then derive one class for each operation.
internal sealed class DoThis : Operation
{
internal DoThis(MyForm form) : base(form) { }
public override String DisplayName
{
get { return "Do this!"; }
}
internal override void Execute()
{
// Code to do this. You can use this.form to interact with
// your form from this operation.
}
}
internal sealed class DoSomethingElse : Operation
{
internal DoSomethingElse(MyForm form) : base(form) { }
public override String DisplayName
{
get { return "Do something else!"; }
}
internal override void Execute()
{
// Code to do something else.
}
}
Now you can add all your operations to the list box
this.lsitBox.Items.Add(new DoThis(this));
this.lsitBox.Items.Add(new DoSomethingElse(this));
and set the display member property.
this.listBox.DisplayMember = "DisplayName";
Finally execute the selected operation in the event handler.
((Operation)this.listBox.SelectedItem).Execute();
This pattern gives clean separation between all your operations and makes future extensions easy and clean. For example you could add a property CanExecute to all operations if you have to check if a operation is currently available. Or if you have to support localization it is easy to add logic for presenting the name of the operation in the current UI language.
Another scenario that is easily supported is if you have some code common to all operations for example logging, security checks, performance measuring and things like that.
internal abstract class Operation
{
protected readonly MyForm form = null;
protected Operation(MyForm form)
{
this.form = form;
}
public abstract String DisplayName { get; }
protected abstract void ExecuteCore();
internal void Execute()
{
Logger.Log("Executing operation " + this.DisplayName);
try
{
this.ExecuteCore();
Logger.Log("Executing operation " + this.DisplayName + " succeeded.");
}
catch (Exception exception)
{
Logger.Log("Executing operation " + this.DisplayName + " failed.", exception);
throw;
}
}
}
Note that you now have to override ExecuteCore() instead of Execute().
One final thought - using an interface IOperation instead or in combination with the abstract base class may be helpful, too. This removes the need that all operation inherit from the same base class because this might sometimes be inconvenient. But I omitted this to not overengineere this even more.
You can't* enumerate case of switch with normal code.
What you can do instead is to replace switch with map of "action name" to "action handler" and than you'll be able to reuse this map for list of action names listbox. See Tilak's answer for sample.
*) If you are really inquisitive you can enumerate choices of switch. C# code is transformed to IL and IL can be read with code. So you can get IL for a method, write (or get existing - Parser for C#) parser for IL and find implementation of switch inside the method, pick all cases. You can even go straight to C# source at build time - but it is even more involved than IL parsing.
Yes there is a way to do this by making a dictionary of lambdas.
void Main()
{
// set up your dictionary
Dictionary<string,Action> myList = new Dictionary<string,Action> {
{ "one", () => { Console.WriteLine("One function"); } },
{ "two", () => { Console.WriteLine("Two function"); }},
{ "three", () => { Console.WriteLine("Three function"); }}
};
// do a "switch" (that is invoke a function that corresponds to a name)
myList["one"]();
// loop the list of keys (that is get a list of all the names)
foreach (string key in myList.Keys)
Console.WriteLine(key);
}
the output of this program:
One function
one
two
three
Also note -- you can add to this "switch" dynamically like this (which is cool and something you can't do with a classical switch statement.)
myList.Add("four",() => { Console.WriteLine("Four function is dynamic"); });
It sounds to me like the number of cases in your switch are going to change a lot. If this is true, then you might want to consider using a mechanism other than a switch statement. Perhaps you want to do something like Alexi Levenkov suggests, and then iterate a list of the stored Action Names and execute the associated handler. This way you will avoid having to add the action name to the action map and then add it to the switch.

Are Enum the right way to go here?

I'm not sure if I am abusing Enums here. Maybe this is not the best design approach.
I have a enum which declares the possible parameters to method which executes batch files.
public enum BatchFile
{
batch1,
batch2
}
I then have my method:
public void ExecuteBatch(BatchFile batchFile)
{
string batchFileName;
...
switch (batchFile)
{
case BatchFile.batch1:
batchFileName = "Batch1.bat";
break;
case BatchFile.batch2:
batchFileName = "Batch2.bat";
break;
default:
break;
}
...
ExecuteBatchFile(batchFileName);
}
So I was wondering if this is sound design.
Another option I was thinking was creating a Dictionary<> in the constructor like this:
Dictionary<BatchFile, String> batchFileName = new Dictionary<BatchFile, string>();
batchFileName.Add(BatchFile.batch1, "batch1.bat");
batchFileName.Add(BatchFile.batch2, "batch2.bat");
Then instead of using a switch statement I would just go:
public void ExecuteBatch(BatchFile batchFile)
{
ExecuteBatchFile(batchFileName[batchFile]);
}
I'm guessing the latter is the better approach.
I'd probably go for a design along these lines:
public interface IBatchFile
{
void Execute();
}
public class BatchFileType1 : IBatchFile
{
private string _filename;
public BatchFileType1(string filename)
{
_filename = filename;
}
...
public void Execute()
{
...
}
}
public class BatchFileType2 : IBatchFile
{
private string _filename;
public BatchFileType2(string filename)
{
_filename = filename;
}
...
public void Execute()
{
...
}
}
In fact, I'd extract any common functionality into a BatchFile base class
What if you suddenly need a third batch file? You have to modify your code, recompile your library and everybody who uses it, has to do the same.
Whenever I find myself writing magic strings that might change, I consider putting them into an extra configuration file, keeping the data out of the code.
I would personally use a static class of constants in this case:
public static class BatchFiles
{
public const string batch1 = "batch1.bat";
public const string batch2 = "batch2.bat";
}
If you want to use an enum then you may want to consider utilising attributes so you can store additional inforation (such as the file name) against the elements.
Here's some sample code to demonstrate how to declare the attributes:
using System;
public enum BatchFile
{
[BatchFile("Batch1.bat")]
batch1,
[BatchFile("Batch2.bat")]
batch2
}
public class BatchFileAttribute : Attribute
{
public string FileName;
public BatchFileAttribute(string fileName) { FileName = fileName; }
}
public class Test
{
public static string GetFileName(Enum enumConstant)
{
if (enumConstant == null)
return string.Empty;
System.Reflection.FieldInfo fi = enumConstant.GetType().GetField(enumConstant.ToString());
BatchFileAttribute[] aattr = ((BatchFileAttribute[])(fi.GetCustomAttributes(typeof(BatchFileAttribute), false)));
if (aattr.Length > 0)
return aattr[0].FileName;
else
return enumConstant.ToString();
}
}
To get the file name simply call:
string fileName = Test.GetFileName(BatchFile.batch1);
I think the latter approach is better because it separates out concerns. You have a method which is dedicated to associating the enum values with a physical path and a separate method for actually executing the result. The first attempt mixed these two approaches slightly.
However I think that using a switch statement to get the path is also a valid approach. Enums are in many ways meant to be switched upon.
Using enums is ok if you don't need to add new batch files without recompiling / redeploying your application... however I think most flexible approach is to define a list of key / filename pairs in your config.
To add a new batch file you just add it to the config file / restart / tell your user the key. You just need to handle unknown key / file not found exceptions.
Is it really necessary that ExecuteBatch works on limited number of possible file names only?
Why don't you just make it
public void ExecuteBatch(string batchFile)
{
ExecuteBatchFile(batchFile);
}
The problem with the latter case is if something passed an invalid value that is not inside the dictionary. The default inside the switch statement provides an easy way out.
But...if you're enum is going to have a lot of entries. Dictionary might be a better way to go.
Either way, I'd recommend some way to provide protection of the input value from causing a bad error even in ammoQ's answer.
The second approach is better, because it links the batch file objects (enums) with the strings..
But talking about design, it would not be very good to keep the enum and the dictionary separate; you could consider this as an alternative:
public class BatchFile {
private batchFileName;
private BatchFile(String filename) {
this.batchFileName = filename;
}
public const static BatchFile batch1 = new BatchFile("file1");
public const static BatchFile batch2 = new BatchFile("file2");
public String getFileName() { return batchFileName; }
}
You can choose to keep the constructor private, or make it public.
Cheers,
jrh.
The first solution (the switch) is simple and straight forward, and you really don't have to make it more complicated than that.
An alternative to using an enum could be to use properties that returns instances of a class with the relevant data set. This is quite expandable; if you later on need the Execute method to work differently for some batches, you can just let a property return a subclass with a different implementation and it's still called in the same way.
public class BatchFile {
private string _fileName;
private BatchFile(string fileName) {
_fileName = fileName;
}
public BatchFile Batch1 { get { return new BatchFile("Batch1.bat"); } }
public BatchFile Batch2 { get { return new BatchFile("Batch2.bat"); } }
public virtual void Execute() {
ExecuteBatchFile(_fileName);
}
}
Usage:
BatchFile.Batch1.Execute();

Categories