views:

235

answers:

9

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.

+2  A: 

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).

jheddings
You need to find out first what the aim of the original code is. It could be that a switch is completely unnecessary.
Wim Hollebrandse
Like the approach with the custom attributes, but I have doubts about the performance. Since this involves reflections, it might get terribly slow. If performance is not an issue though, this is a quite elegant solution.
Rob van Groenewoud
Inspired by this answer I implemented another solution. See my answer.
Rob van Groenewoud
+1  A: 

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.

Wim Hollebrandse
+2  A: 

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.

epotter
A: 

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.

ebpower
+4  A: 

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.

Nestor
+1, that is a very handy tip indeed :)
Russell
That will still require the full switch statement, though, which the OP was trying to avoid.
jheddings
A: 

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?

mrnye
If you change the List<StringHandler> to a Dictionary<string,Action> you could leave the lookup to the dictionary and simply execute the action delegate you have added for the specified key.
Rob van Groenewoud
To see an example of my other comment, see my answer, it doesn't use Action but the dictionary part is similar.
Rob van Groenewoud
A: 

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!

Edward Leno
+1  A: 

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}
Wim Coenen
A: 

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&lt;string, MethodInfo&gt;();
        //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 =&gt; Attribute.GetCustomAttribute(method, typeof(KeywordsAttribute)) is KeywordsAttribute &amp;&amp;
                  method.GetParameters().Length == 1 &amp;&amp;
                  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&lt;string&gt; Keywords
    {
        get { return keywords; }
    }
    
    }

    }

Apologies for any strange rendering, for some reason the syntax highlighting chokes on the code :-(

Rob van Groenewoud