5
\$\begingroup\$

Recently I wrote this class and it has a enum of all the interaction types a game item can have. What I want to know is, it seems a bit of a stretch to have a huge enum then that long switch, is there any way I can shorten it or improve it without changing it completely?

namespace Sirius.Base.Game.Other.Items
{
    public enum InteractionType
    {
        None,
        Gate,
        Postit,
        Moodlight,
        Trophy,
        Bed,
        Scoreboard,
        VendingMachine,
        Alert,
        OneWayGate,
        LoveShuffler,
        HabboWheel,
        Dice,
        Bottle,
        Hopper,
        Teleport,
        Pool,
        Roller,
        FootballGate,
        Pet0,
        Pet1,
        Pet2,
        Pet3,
        Pet4,
        Pet5,
        Pet6,
        Pet7,
        Pet8,
        Pet9,
        Pet10,
        Pet11,
        Pet12,
        Pet13,
        Pet14,
        Pet15,
        Pet16,
        Pet17,
        Pet18,
        Pet19,
        Pet20,
        Pet21,
        Pet22,
        Pet28,
        Pet29,
        Pet30,
        IceSkates,
        NormalSkates,
        Lowpool,
        Haloweenpool,
        Football,
        FootballGoalGreen,
        FootballGoalYellow,
        FootballGoalBlue,
        FootballGoalRed,
        Footballcountergreen,
        Footballcounteryellow,
        Footballcounterblue,
        Footballcounterred,
        Banzaigateblue,
        Banzaigatered,
        Banzaigateyellow,
        Banzaigategreen,
        Banzaifloor,
        Banzaiscoreblue,
        Banzaiscorered,
        Banzaiscoreyellow,
        Banzaiscoregreen,
        Banzaicounter,
        Banzaitele,
        Banzaipuck,
        Banzaipyramid,
        Freezetimer,
        Freezeexit,
        Freezeredcounter,
        Freezebluecounter,
        Freezeyellowcounter,
        Freezegreencounter,
        FreezeYellowGate,
        FreezeRedGate,
        FreezeGreenGate,
        FreezeBlueGate,
        FreezeTileBlock,
        FreezeTile,
        Jukebox,
        MusicDisc,
        PuzzleBox,
        Toner,
        PressurePad,
        WfFloorSwitch1,
        WfFloorSwitch2,
        Gift,
        Background,
        Mannequin,
        GateVip,
        GuildItem,
        GuildGate,
        GuildForum,
        Tent,
        TentSmall,
        BadgeDisplay,
        Stacktool,
        Television,
        WiredEffect,
        WiredTrigger,
        WiredCondition,
        Wallpaper,
        Floor,
        Landscape,
        Badge,
        CrackableEgg,
        Effect,
        Deal,
        HorseSaddle1,
        HorseSaddle2,
        HorseHairstyle,
        HorseBodyDye,
        HorseHairDye,
        GnomeBox,
        Bot,
        PurchasableClothing,
        PetBreedingBox,
        Arrow,
        Lovelock,
        MonsterplantSeed,
        Cannon,
        Counter,
        CameraPicture,
        FxProvider
    }


    internal static class InteractionTypes
    {
        internal static InteractionType GetTypeFromString(string pType)
        {
            switch (pType.ToLower())
            {
                case "":
                case "default":
                    return InteractionType.None;
                case "gate":
                    return InteractionType.Gate;
                case "postit":
                    return InteractionType.Postit;
                case "dimmer":
                    return InteractionType.Moodlight;
                case "trophy":
                    return InteractionType.Trophy;
                case "bed":
                    return InteractionType.Bed;
                case "scoreboard":
                    return InteractionType.Scoreboard;
                case "vendingmachine":
                    return InteractionType.VendingMachine;
                case "alert":
                    return InteractionType.Alert;
                case "onewaygate":
                    return InteractionType.OneWayGate;
                case "loveshuffler":
                    return InteractionType.LoveShuffler;
                case "habbowheel":
                    return InteractionType.HabboWheel;
                case "dice":
                    return InteractionType.Dice;
                case "hopper":
                    return InteractionType.Hopper;
                case "bottle":
                    return InteractionType.Bottle;
                case "teleport":
                    return InteractionType.Teleport;
                case "pool":
                    return InteractionType.Pool;
                case "roller":
                    return InteractionType.Roller;
                case "fbgate":
                    return InteractionType.FootballGate;
                case "pet0":
                    return InteractionType.Pet0;
                case "pet1":
                    return InteractionType.Pet1;
                case "pet2":
                    return InteractionType.Pet2;
                case "pet3":
                    return InteractionType.Pet3;
                case "pet4":
                    return InteractionType.Pet4;
                case "pet5":
                    return InteractionType.Pet5;
                case "pet6":
                    return InteractionType.Pet6;
                case "pet7":
                    return InteractionType.Pet7;
                case "pet8":
                    return InteractionType.Pet8;
                case "pet9":
                    return InteractionType.Pet9;
                case "pet10":
                    return InteractionType.Pet10;
                case "pet11":
                    return InteractionType.Pet11;
                case "pet12":
                    return InteractionType.Pet12;
                case "pet13":
                    return InteractionType.Pet13;
                case "pet14":
                    return InteractionType.Pet14;
                case "pet15":
                    return InteractionType.Pet15;
                case "pet16":
                    return InteractionType.Pet16;
                case "pet17":
                    return InteractionType.Pet17;
                case "pet18":
                    return InteractionType.Pet18;
                case "pet19":
                    return InteractionType.Pet19;
                case "pet20":
                    return InteractionType.Pet20;
                case "pet21":
                    return InteractionType.Pet21;
                case "pet22":
                    return InteractionType.Pet22;
                case "pet28":
                    return InteractionType.Pet28;
                case "pet29":
                    return InteractionType.Pet29;
                case "pet30":
                    return InteractionType.Pet30;
                case "iceskates":
                    return InteractionType.IceSkates;
                case "rollerskate":
                    return InteractionType.NormalSkates;
                case "lowpool":
                    return InteractionType.Lowpool;
                case "haloweenpool":
                    return InteractionType.Haloweenpool;
                case "ball":
                    return InteractionType.Football;
                case "green_goal":
                    return InteractionType.FootballGoalGreen;
                case "yellow_goal":
                    return InteractionType.FootballGoalYellow;
                case "red_goal":
                    return InteractionType.FootballGoalRed;
                case "blue_goal":
                    return InteractionType.FootballGoalBlue;
                case "green_score":
                    return InteractionType.Footballcountergreen;
                case "yellow_score":
                    return InteractionType.Footballcounteryellow;
                case "blue_score":
                    return InteractionType.Footballcounterblue;
                case "red_score":
                    return InteractionType.Footballcounterred;
                case "bb_blue_gate":
                    return InteractionType.Banzaigateblue;
                case "bb_red_gate":
                    return InteractionType.Banzaigatered;
                case "bb_yellow_gate":
                    return InteractionType.Banzaigateyellow;
                case "bb_green_gate":
                    return InteractionType.Banzaigategreen;
                case "bb_patch":
                    return InteractionType.Banzaifloor;
                case "bb_blue_score":
                    return InteractionType.Banzaiscoreblue;
                case "bb_red_score":
                    return InteractionType.Banzaiscorered;
                case "bb_yellow_score":
                    return InteractionType.Banzaiscoreyellow;
                case "bb_green_score":
                    return InteractionType.Banzaiscoregreen;
                case "banzaicounter":
                    return InteractionType.Banzaicounter;
                case "bb_teleport":
                    return InteractionType.Banzaitele;
                case "banzaipuck":
                    return InteractionType.Banzaipuck;
                case "bb_pyramid":
                    return InteractionType.Banzaipyramid;
                case "freezetimer":
                    return InteractionType.Freezetimer;
                case "freezeexit":
                    return InteractionType.Freezeexit;
                case "freezeredcounter":
                    return InteractionType.Freezeredcounter;
                case "freezebluecounter":
                    return InteractionType.Freezebluecounter;
                case "freezeyellowcounter":
                    return InteractionType.Freezeyellowcounter;
                case "freezegreencounter":
                    return InteractionType.Freezegreencounter;
                case "freezeyellowgate":
                    return InteractionType.FreezeYellowGate;
                case "freezeredgate":
                    return InteractionType.FreezeRedGate;
                case "freezegreengate":
                    return InteractionType.FreezeGreenGate;
                case "freezebluegate":
                    return InteractionType.FreezeBlueGate;
                case "freezetileblock":
                    return InteractionType.FreezeTileBlock;
                case "freezetile":
                    return InteractionType.FreezeTile;
                case "jukebox":
                    return InteractionType.Jukebox;
                case "musicdisc":
                    return InteractionType.MusicDisc;
                case "pressure_pad":
                    return InteractionType.PressurePad;
                case "wf_floor_switch1":
                    return InteractionType.WfFloorSwitch1;
                case "wf_floor_switch2":
                    return InteractionType.WfFloorSwitch2;
                case "puzzlebox":
                    return InteractionType.PuzzleBox;
                case "water":
                    return InteractionType.Pool;
                case "gift":
                    return InteractionType.Gift;
                case "background":
                    return InteractionType.Background;
                case "mannequin":
                    return InteractionType.Mannequin;
                case "vip_gate":
                    return InteractionType.GateVip;
                case "roombg":
                    return InteractionType.Toner;
                case "gld_item":
                    return InteractionType.GuildItem;
                case "gld_gate":
                    return InteractionType.GuildGate;
                case "guild_forum":
                    return InteractionType.GuildForum;
                case "tent":
                    return InteractionType.Tent;
                case "tent_small":
                    return InteractionType.TentSmall;
                case "badge_display":
                    return InteractionType.BadgeDisplay;
                case "stacktool":
                    return InteractionType.Stacktool;
                case "television":
                    return InteractionType.Television;
                case "wired_effect":
                    return InteractionType.WiredEffect;
                case "wired_trigger":
                    return InteractionType.WiredTrigger;
                case "wired_condition":
                    return InteractionType.WiredCondition;
                case "floor":
                    return InteractionType.Floor;
                case "wallpaper":
                    return InteractionType.Wallpaper;
                case "landscape":
                    return InteractionType.Landscape;
                case "badge":
                    return InteractionType.Badge;
                case "crackable_egg":
                    return InteractionType.CrackableEgg;
                case "effect":
                    return InteractionType.Effect;
                case "deal":
                    return InteractionType.Deal;
                case "horse_saddle_1":
                    return InteractionType.HorseSaddle1;
                case "horse_saddle_2":
                    return InteractionType.HorseSaddle2;
                case "horse_hairstyle":
                    return InteractionType.HorseHairstyle;
                case "horse_body_dye":
                    return InteractionType.HorseBodyDye;
                case "horse_hair_dye":
                    return InteractionType.HorseHairDye;
                case "gnome_box":
                    return InteractionType.GnomeBox;
                case "bot":
                    return InteractionType.Bot;
                case "purchasable_clothing":
                    return InteractionType.PurchasableClothing;
                case "pet_breeding_box":
                    return InteractionType.PetBreedingBox;
                case "arrow":
                    return InteractionType.Arrow;
                case "lovelock":
                    return InteractionType.Lovelock;
                case "cannon":
                    return InteractionType.Cannon;
                case "counter":
                    return InteractionType.Counter;
                case "camera_picture":
                    return InteractionType.CameraPicture;
                case "fx_provider":
                    return InteractionType.FxProvider;
                default:
                    return InteractionType.None;
            }
        }
    }
}
\$\endgroup\$
3
  • 2
    \$\begingroup\$ The code looks OK to me but the question is why? Why have text entry? Present the user with a list of options to select from. How is this being used? \$\endgroup\$
    – paparazzo
    Feb 16, 2017 at 14:33
  • 7
    \$\begingroup\$ The title applies to every question on CR... I'm sure you can come up with something appropriate. Apart from that I agree with @Paparazzi. \$\endgroup\$
    – t3chb0t
    Feb 16, 2017 at 15:11
  • 1
    \$\begingroup\$ How to improve this depends a lot on how it's meant to be used. Some questions: why are there 30 different Pet interactions? Are they really that different? What happens when another pet gets added? The same goes for the various colored interactions. And how does the code that handles all these interactions look? In other words: in what context is this code is used? \$\endgroup\$ Feb 16, 2017 at 19:36

5 Answers 5

6
\$\begingroup\$

Update

Added constructor parameter for the dictionary to avoid lower/capital letter case problems as pointed in the comments.

There are 2 solutions:

First option would be to map string -> InteractionType like this:

private static readonly Dictionary<string, InteractionType> InteractionTypesDictionary = new Dictionary
    <string, InteractionType>(StringComparer.CurrentCultureIgnoreCase)
    {
        [string.Empty] = InteractionType.None,
        ["default"] = InteractionType.None,
        ["gate"] = InteractionType.Gate,
        ["postit"] = InteractionType.Postit,
        //...
    };

Once you have all of your strings there your method will be just 1 line:

internal static InteractionType GetTypeFromString(string pType)
{
    string key = pType.ToLower();
    if(InteractionTypesDictionary.ContainsKey(key))
    {
        return InteractionTypesDictionary[key];
    }
    return InteractionType.None; // you might want to throw exception here.
}

The second option is the one I would prefer, I don't see any reason to not have the same string name as the enum, it's easier to know what exact value you are looking for and you can use some of the predefined functions in the Enum class. This will make your code just a few lines long:

internal static InteractionType GetTypeFromString(string pType)
{
    string[] enumNames = Enum.GetNames(typeof(InteractionType));
    string enumValueName = enumNames.FirstOrDefault(x => pType.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);
    if (enumValueName != null)
    {
        return (InteractionType) Enum.Parse(typeof(InteractionType), enumValueName);
    }
    return InteractionType.None;
}

This is the best solution because the previous 2 violate in some way the Open-Closed principle, because if you add some new value into the enum, you will need to modify some code somewhere in your project, but like this it will work even if you add new values, without updating this specific method. Also this is the shortest version, it simply requires you to have proper strings as parameters e.g

bb_blue_gate

InteractionType.Banzaigateblue

This is wrong and wont work it should be:

banzaigateblue // lower or capital letters dont matter

InteractionType.Banzaigateblue

\$\endgroup\$
13
  • 5
    \$\begingroup\$ You can use the Dictionary constructor that takes a comparison - use StringComparer.OrdinalIgnoreCase and that ToLower() call can be removed. \$\endgroup\$ Feb 16, 2017 at 15:05
  • 1
    \$\begingroup\$ With the dictionary method, what would happened if an invalid key is entered? When coordinating user input with a dictionary I always use ContainsKey() before attempting to retrieve the value for the key to avoid an unhandled error. \$\endgroup\$ Feb 16, 2017 at 16:56
  • 1
    \$\begingroup\$ @ChandlerTurner Good point. \$\endgroup\$
    – Denis
    Feb 16, 2017 at 17:02
  • 5
    \$\begingroup\$ I would use TryGetValue over a combination of ContainsKey and indexer. \$\endgroup\$
    – Dan Lyons
    Feb 16, 2017 at 18:41
  • 2
    \$\begingroup\$ Tie enum name to UI name is introducing a dependency \$\endgroup\$
    – paparazzo
    Feb 17, 2017 at 16:28
4
\$\begingroup\$

Load a Dictionary with the descriptions for lookup via code

The benefit is you have the data in one spot

public enum RORW { [Description("Read Only")] RO, [Description("Read Write")] RW };

Need using System.ComponentModel;

Load Dictionary

var type = typeof(RORW);
Dictionary<string, RORW> EnumLookup = new Dictionary<string, RORW>(StringComparer.CurrentCultureIgnoreCase);
foreach (RORW rorw in Enum.GetValues(typeof(RORW)))
{
    Debug.WriteLine(rorw.ToString());
    var memInfo = type.GetMember(rorw.ToString());
    var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
    var description = ((DescriptionAttribute)attributes[0]).Description;
    Debug.WriteLine(description);
    EnumLookup.Add(description, rorw);
}
\$\endgroup\$
2
  • \$\begingroup\$ I like this method, but I would use a custom attribute instead to stay true to the nature of the description attribute. \$\endgroup\$ Feb 16, 2017 at 19:40
  • \$\begingroup\$ According to this stackoverflow.com/questions/72696/… it seems like OrdinalIgnoreCase would perform better than CurrentCultureIgnoreCase \$\endgroup\$
    – Svek
    Feb 18, 2017 at 3:59
3
\$\begingroup\$

This isn't the most performant method, but it does simplify and localize the code.

First I would build a custom attribute to hold the string value.

class InputKeyAttribute : Attribute
{
    public string Value { get; private set; }
    public InputKeyAttribute (string value)
    {
        Value = value;
    }
}

And create the extension methods to retrieve the information needed

// This will retrieve the input key
public static string GetInputKey(this Enum value)
{
    Type type = value.GetType();
    string name = Enum.GetName(type, value);
    if (name != null)
    {
        FieldInfo field = type.GetField(name);
        if (field != null)
        {
            InputKeyAttribute attr = 
                   Attribute.GetCustomAttribute(field, 
                     typeof(InputKeyAttribute )) as InputKeyAttribute ;
            if (attr != null)
            {
                return attr.Value;
            }
        }
    }
    return null;
}

// get the enum for the string value. returns 0(None) if not match found
public InteractionType GetInteractionType(this string key)
{
    return Enum.GetValues(typeof(InteractionType )).Cast<InteractionType>()
            .FirstOrDefault(v => String.Equals(v.GetInputKey(), key,
               StringComparison.OrdinalIgnoreCase));
}

Now you can add the attribute to the enum

public enum InteractionType
{
    [InputKey = "Default"]
    None,
    [InputKey = "Gate"]
    Gate,
    [InputKey = "Postit"]
    Postit
    //etc...
}

And then retrieve the value when needed

var InteractionType = ptype.GetInteractiontype();

To solve the performance issue you could implement this to create the lookup dictionary and call from it instead of iterating through all of the enum values on each call. After the initialization of the dictionary, the retrieval call should perform in O(1) time.

private Dictionary<string, InteractionType> interactionTypes;

public InteractionType GetInteractionType(this string key)
{
    if(interactionTypes == null)
    {
        interactionTypes = CreateInteractionDictionary();
    }

    if(interactionTypes.ContainsKey(key))
    {
        return interactionTypes[key];
    }
    else
    {
    return InteractionType.None;
    }
}

private Dictionary<string, InteractionType> CreateInputDictionary()
{
    Dictionary<string, InteractionType> output = new Dictionary<string, InteractionType>(StringComparer.OrdinalIgnoreCase);
    foreach(var value in Enum.GetValues(typeof(InteractionType )).Cast<InteractionType>())
    {
        output.Add(value.GetInputKey(), value);
    }
    return output;
}
\$\endgroup\$
5
  • \$\begingroup\$ You could also build this with generics to make it work for multiple enum types. Let me know if you would be interested in seeing this. \$\endgroup\$ Feb 16, 2017 at 15:52
  • 1
    \$\begingroup\$ I agree with you that this is the best approach. I'm using this as generic way by my own and it's really smart. +1 \$\endgroup\$
    – Sebi
    Feb 17, 2017 at 11:02
  • 1
    \$\begingroup\$ Like you said in your opening statement, the performance of this is really no ideal. The use-case in the question at hand looks like the enum is going to be used heavily, and the performance may suffer overall compared to the existing switch statement. \$\endgroup\$
    – Svek
    Feb 17, 2017 at 15:13
  • \$\begingroup\$ Thanks for the feedback. This is a really lazy implementation, but I think it can help with the clarification of the code. The best way to use this would be to create a dictionary dynamically with this layout. That would only take one iteration of the enumerations and the calls to the dictionary would be in O(1). \$\endgroup\$ Feb 17, 2017 at 19:44
  • \$\begingroup\$ @Svek I have added this idea of the dictionary as an edit. I think that the switch method would be more performant, but this method would allow the user to create a list of possible inputs for the user. I know this violates the extreme programming methodology of not creating something until it's needed, but I would assume this is needed already. \$\endgroup\$ Feb 17, 2017 at 20:21
2
\$\begingroup\$

Design Review

I challenge the need for a huge enum like this.

Even if you find a succint and maintainable way to map this huge enum from a string, what does this provide you in your code? Do you now have a method that needs a huge switch case on the enum to decide proper actions when the interaction is triggered?

void InteractWith(InteractionType interaction)
{
   switch (interaction)
   {
       case InteractionType.Gate:
           InteractWithAGate();
           break;
       case InteractionType.Moodlight:
           InteractWithAMoodlight();
           break;
       // .. and here we go again
   }
}

Let's consider the SOLID principles. We are in particular interested in Interface segregation principle and just common sense when dealing with OO-design.

Many client-specific interfaces are better than one general-purpose interface.

I would make many different enums, each containing a subset of interactions. I'm also not convinced of the name interaction. Many enum values appear to be assets or items.

enum GateKind
{
    Gate,
    OneWayGate,
    // .. others
}

And have specific methods or even classes that handle these specific interaction types. Some trivial example of a game where a player interacts with items, such as a gate.

class GateState
{
    void HandlePlayerInteraction(Player player, GateKind gate)
    {
       if (player.WantsToEnter(gate))
       {
           player.Enter(gate);
       }
       else
       {
           player.GoBack();
       }
    }
}
\$\endgroup\$
0
\$\begingroup\$

Alternative: Use static fields

An alternative solution would eliminate both the enum and the switch, by using static fields instead.

This solution has the advantage of not having to do any bridging/mapping of the string to an enum, while making your code look neat and tidy.

public static class InteractionType
{
    public static readonly string None = "";
    public static readonly string Gate = "gate";
    public static readonly string Postit = "postit";
    public static readonly string Moodlight = "dimmer";
    public static readonly string Trophy = "trophy";
    ...
}

However, among other disadvantages, the one that stands out against your provided code is going to be the default cases, as it would require you deal with assigning the default/unmapped cases manually to None. (i.e. "default" has to be turned into "")

You could fix that by using reflection and enumerate the static fields, to create a List<string> to aid your "cleaning up" of the string, so that it will match up with the static string (our enum/switch replacement).

private static readonly List<string> _interactionTypes = typeof(InteractionType)
    .GetFields(BindingFlags.Static | BindingFlags.Public)
    .Select(_ => _.GetValue(null).ToString())
    .ToList();

public string MatchValidOrDefault(string value)
{
    if (value == null) throw new ArgumentNullException(nameof(value));

    if(_interactionTypes.Contains(value) return value; //exact matches only
    // you can also do alternative matching, such as
    // if(_interactionTypes.Contains(value), StringComparer.CurrentCultureIgnoreCase) return value.ToLower();
    return InteractionType.None;
}

Additional things to consider

Note that this also means that a single static field can be mapped to the same string, for example:

public static readonly string Foo = "foo";
public static readonly string Bar = "foo";

Where the following statement is true...

InteractionType.Foo == InteractionType.Bar // "foo" == "foo"

This solution allows multiple "enums" (static fields) to be mapped to a single string. Which can be useful depending on the requirements of your program.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.