2

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?

Fuzz Evans
  • 2,893
  • 11
  • 44
  • 63
  • 4
    Your example makes no sense. – Hogan Jan 09 '13 at 18:59
  • 2
    @Hogan, it is example of what Fuzz wants to achieve: which is enumerate all cases of switch... – Alexei Levenkov Jan 09 '13 at 19:01
  • I understand what you mean, you want to programmatically generate the cases in your switch, but you actually shouldn't need to do this. There will be another way to do whatever it is you're trying to do such as taking the selected index of the listbox. – Mitch Jan 09 '13 at 19:03
  • @Hogan, Alexei is right. That was just an example, totally made up and obviously won't work, that was just what I figured would be an example of what I need to do. – Fuzz Evans Jan 09 '13 at 19:59
  • @FuzzEvans - I understood your question from the question part. (As you can see from my answer). I was just pointing out the example you gave was not 100% clear. All good :) – Hogan Jan 09 '13 at 20:13

5 Answers5

4

Dictionary<string, Action> is the way to avoid. Dictionary.Keys becomes ListBox.Items.

switch(n) becomes

var action = dict[n] as Action
action();
Tilak
  • 30,108
  • 19
  • 83
  • 131
  • 1
    where is c#? Neither in the question, nor in the answer. I missed one semicolon, but the code is only for illustration. – Tilak Jan 09 '13 at 19:17
  • 1
    [as c#](http://msdn.microsoft.com/en-us/library/cscsdfbt(v=vs.71).aspx), `as` is equivalent to `is` + Cast if not null – Tilak Jan 09 '13 at 19:22
  • you should bound ListBox, and Add keys to bound data source. Add like `foreach (var key in dictionary.Keys) { data.Add(key);}` – Tilak Jan 10 '13 at 16:43
  • I was able to add the dictionary keys to the list box with a foreach loop as shown in Hogans answer, but I'm not clear on how to implement the action now. See revision to op. – Fuzz Evans Jan 10 '13 at 16:54
3

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.

Daniel Brückner
  • 59,031
  • 16
  • 99
  • 143
  • +1. Nice and detailed explanation for complete approach. I'd consider it overkill for basic switch, but OP's case sound like it would benefit from more structured approach shown here. – Alexei Levenkov Jan 09 '13 at 21:53
  • Thank you for the information. I'm clearly in over my head at this point and will need to do some more research. Thank you for your recommendation though. – Fuzz Evans Jan 10 '13 at 15:37
2

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"); });
Hogan
  • 69,564
  • 10
  • 76
  • 117
2

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.

Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
1

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.

spots
  • 2,483
  • 5
  • 23
  • 38