views:

196

answers:

6

I've recently found myself needing (yes, needing) to define absurdly long switch statements and enum declarations in C# code, but I'm wondering what people feel is the best way to split them into logical subsections. In my situation, both the enum values and the cases (which are based on the enum values) have fairly clear groupings, yet I am slightly unsure how to reflect this in code.

Note that in my code, I have roughly 5 groups of between 10 and 30 enum values/cases each.

The three vaguely sensible options I can envisage are:

  1. Define #region blocks around all logical groups of cases/enum values within the declaration (optionally separated by blank lines).
  2. Comment each group with it's name, with a blank line before each group name comment.
  3. Do nothing whatsoever - simply leave the switch/enum as a huge list of cases/values.

Which do you prefer? Would you treat enums and switches separately? (This would seem slightly odd to me.) Now, I wouldn't say that there is any right/wrong answer to this question, though I would nonetheless be quite interested in hearing what the general consenus of views is.

Note 1: This situation where I might potentially have an extremely long enum declaration of 50/100+ values is unfortunately unavoidable (and similarly with the switch), since I am attempting to write a lexer (tokeniser), and this would thus seem the most reasonable approach for several reasons.

Note 2: I am fully aware that several duplicate questions already exist on the question of whether to use regions in general code (for structuring classes, mainly), but I feel my question here is much more specific and hasn't yet been addressed.

+1  A: 

I would leave it as a huge list of cases/ values.

David Silva Smith
The reason being that you don't like regions in general, I take it? (This may well be a fair point.)
Noldorin
+1 I don't like regions aka code obfuscation.
kenny
I don't like regions in general or in this specific instance :). I like to use ctrl + m + o to collapse class files. Regions break my intention because I see the region instead of the code.
David Silva Smith
A: 

Get rid of the enums and make them into objects. You could then call methods on your objects and keep the code separated, maintainable, and not a nightmare.

There are very few cases when you would actually need to use an enum instead of an object and nobody likes long switch statements.

Bryan Rowe
See my first note. This is a lexer (as part of a compiler) I am writing. The only approach I have seen in existing code is to use an enum - though these examples typically only defined one for a very simple grammar, so didn't really need any grouping.
Noldorin
I don't think that changes my answer. Without seeing the source, I have to believe they can be broken down into value objects.
Bryan Rowe
Unfortunately, I really need to specify the values with a single data structure of some sort. I should have stated that the enum represents a "token kind" in the lexer, and must be stored within a Token struct.
Noldorin
Alright. So why not a token class with a bunch of static constructors for each possible token kind? So you could have something like:public static Token OneTypeOfToken(){ return new Token() }You could pass into the Token ctor a delegate or w/e important functionality you are currently switching on. You would then just have a collection of Tokens and say Token.DoSomething();
Bryan Rowe
@Bryan: That's actually very much how I have it set up at the moment. It doesn't eliminate the need for any of the cases, however, unless I've misunderstood.
Noldorin
+1  A: 

If there are some cases that have the same code block, using the Strategy design pattern, could remove the switch block. This can create a lot of classes to you, but will show how complex it really is, and split the logic in smaller classes.

mkato
Interesting suggestion. I'm not really familiar with the strategy pattern. Is it really relevant here, in the case that my enum represents a set of token kinds (and I'm writing this for a compiler)?
Noldorin
I don't see much problem in using Strategy there.Basically, you create classes responsible to do the logics inside each case block. It's the one who instantiates the bigger class(the one who has the switch blcok) who define which logic class it will use.
mkato
+2  A: 

Sure, region those things up. They probably don't change much, and when they do, you can expand the region, make your changes, collapse it, and move on to the rest of the file.

They are there for a reason, use them to your advantage.

Matthew Vines
This has been pretty much my thinking before I asked this question. I tend to consider overuse of of regions somewhat "evil", but this seems to be an exception.
Noldorin
+2  A: 

You could also have a Dictionary<[your_enum_type], Action> (or Func instead of Action) or something like that (considering your functions have a similar signature). Then you could instead of using a switch, instead of:

        switch (item)
        {
            case Enum1: func1(par1, par2)
                break;
            case Enum2: func2(par1, par2)
                break;
        }

you could have something like:

public class MyClass
{
    Dictionary<int, Action<int, int>> myDictionary;
    //These could have only static methods also
    Group1Object myObject1;
    Group2Object myObject2;

    public MyClass()
    {
        //Again, you wouldn't have to initialize if the functions in them were static
        myObject1 = new Group1Object();
        myObject2 = new Group2Object();
        BuildMyDictionary();
    }

    private Dictionary<int, Action<int, int>> BuildMyDictionary()
    {
        InsertGroup1Functions();
        InsertGroup2Functions();
        //...
    }

    private void InsertGroup2Functions()
    {
        myDictionary.Add(1, group2.AnAction2);
        myDictionary.Add(2, group2.AnotherAction2);
    }

    private void InsertGroup1Functions()
    {
        myDictionary.Add(3, group1.AnAction1);
        myDictionary.Add(4, group1.AnotherAction1);
    }


    public void DoStuff()
    {
        int t = 3; //Get it from wherever
        //instead of switch
        myDictionary[t](arg1, arg2);
    }
}
Samuel Carrijo
This method certainly has its advantages in many circumstances (especially long case blocks). However, for me this is really only deferring the switch statement cases to a method adding items to a Dictionary, and doesn't really help with grouping.
Noldorin
IMHO it's not exactly same - this is a nice way to split those dictionary adding lines into multiple methods. It also gives you an additional level of abstraction through delegates - you can add any delegate to the list, from anywhere in your code.
Groo
@samuel: Thanks for adding the example code. Indeed, the way you can split up the dictionary initialisation into methods is quite advantageous. I'm starting to like this solution now. :)
Noldorin
A: 

Here's a good shortcut for people who use regions.

I was switching between Eclipse and Visual Studio when I tried to go full screen in VS by pressing

Ctrl-M-M

and lo and behold, the region closed and expanded!

Hans Malherbe
Yeah, there are some handy shortcuts, which I do think make regions less of a nuisance than they could potentially be. :)
Noldorin