HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcsharpMinor

how to avoid switch-case

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
caseswitchavoidhow

Problem

How can I refactor this code to avoid switch-cases? Is there a way of changing commands without adding new case and make it dynamically...I want the solution not to depend on the amount of cases. For example, if I have 1000 commands..I should obviously write them all as cases?
I hope my problem is clear.

```
static void Main(string[] args)
{
ConsoleHelp ch1 = new ConsoleHelp();
Plus pl = new Plus();
Minus mn = new Minus();
Divide dv = new Divide();
Multiply mlt = new Multiply();
Sinus sin = new Sinus();
Tangent tan = new Tangent();
Square sq = new Square();
Degree dg = new Degree();
Percent pr = new Percent();

while (true)
{
Console.Write("command> ");
string com = Console.ReadLine();
if (com != null)
{
switch (com.ToLower())
{
case "?":
ch1.Helpper();
break;
case "plus":
pl.Sum();
break;
case "minus":
mn.Minusvalue();
break;
case "divide":
dv.Dividevalue();
break;
case "multiply":
mlt.Multvalue();
break;
case "sinus":
sin.Sinusvalue();
break;
case "tangent":
tan.Tangentvalue();
break;
case "square":
sq.Squarevalue();
break;
case "degree":
dg.Degvalue();
break;
case "percent":
pr.Pervalue();
break;
case "c":
Environment.Exit(0);
break;

default:
Console.WriteLine("The command is not supported. Enter one of the valid comman

Solution

You can create your own Attribute to decorate all of your classes (e.g., Plus, ConsoleHelp, Minus, Divide, and even Exit). In the sample below, the attribute is called SystemCommandAttribute. It would require to specify what the name of the command would be as a constructor parameter (what the user would have to type on the command line).

The next step would be to have each of your command classes implement the same interface. In the example code I used the name ISystemCommand. That interface should have a single method: Execute(). That way we know what to call when we want to invoke the command.

Then you can use reflection to search for all classes that have that attribute and load them into a dictionary of string to ISystemCommand. The GetCommandsFromAssemblies() method handles instantiating each instance of ISystemCommand for you.

From there you can use the dictionary instead of the switch statement.

public class Program
{
    static void Main(string[] args)
    {
        Dictionary commandToType = GetCommandsFromAssemblies();
        RunCommandLoop(commandToType);
    }

    /// 
    /// Loop over all types currently loaded in the AppDomain looking for ISystemCommand
    /// 
    /// A dictionary from command name to instance of command
    private static Dictionary GetCommandsFromAssemblies()
    {
        var typesWithMyAttribute =
            from a in AppDomain.CurrentDomain.GetAssemblies()
            from t in a.GetTypes()
            let attributes = t.GetCustomAttributes(typeof(SystemCommandAttribute), true)
            where attributes != null && attributes.Length > 0
            select new
            {
                Instance = (ISystemCommand)Activator.CreateInstance(t),
                CommandName = attributes.Cast().Single().CommandName
            };

        Dictionary commandToType = typesWithMyAttribute.ToDictionary(t => t.CommandName, t => t.Instance);

        return commandToType;
    }

    private static void RunCommandLoop(Dictionary actions)
    {
        while (true)
        {
            Console.Write("command> ");
            string com = Console.ReadLine();
            if (com != null)
            {
                ISystemCommand action;

                if (actions.TryGetValue(com, out action))
                {
                    action.Execute();
                }
                else
                {
                    Console.WriteLine("The command is not supported. Enter one of the valid commands.");
                    break;
                }
            }
        }
        Console.ReadLine();
    }
}

public interface ISystemCommand
{
    void Execute();
}

public class SystemCommandAttribute : Attribute
{
    public SystemCommandAttribute(string commandName)
    {
        CommandName = commandName;
    }

    public string CommandName { get; set; }
}

[SystemCommand("plus")]
public class Plus : ISystemCommand
{
    public void Execute()
    {
        Sum();
    }

    private void Sum()
    {
        // your existing Sum logic here
        Console.WriteLine("Sum called");
    }
}

[SystemCommand("?")]
public class ConsoleHelp : ISystemCommand
{
    public void Execute()
    {
        Helper();
    }

    private void Helper()
    {
        // your existing Helper logic here
        Console.WriteLine("Helper called");
    }
}

[SystemCommand("c")]
public class ExitCommand : ISystemCommand
{
    public void Execute()
    {
        Environment.Exit(0);
    }
}


To add a new command in the future, all you would need to do would be to

  • create your command class



  • Inherit from ISystmeCommand



  • Add the SystmCommandAttribute



Your main program would not need to change at all.

Code Snippets

public class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, ISystemCommand> commandToType = GetCommandsFromAssemblies();
        RunCommandLoop(commandToType);
    }

    /// <summary>
    /// Loop over all types currently loaded in the AppDomain looking for ISystemCommand
    /// </summary>
    /// <returns>A dictionary from command name to instance of command</returns>
    private static Dictionary<string, ISystemCommand> GetCommandsFromAssemblies()
    {
        var typesWithMyAttribute =
            from a in AppDomain.CurrentDomain.GetAssemblies()
            from t in a.GetTypes()
            let attributes = t.GetCustomAttributes(typeof(SystemCommandAttribute), true)
            where attributes != null && attributes.Length > 0
            select new
            {
                Instance = (ISystemCommand)Activator.CreateInstance(t),
                CommandName = attributes.Cast<SystemCommandAttribute>().Single().CommandName
            };

        Dictionary<string, ISystemCommand> commandToType = typesWithMyAttribute.ToDictionary(t => t.CommandName, t => t.Instance);

        return commandToType;
    }

    private static void RunCommandLoop(Dictionary<string, ISystemCommand> actions)
    {
        while (true)
        {
            Console.Write("command> ");
            string com = Console.ReadLine();
            if (com != null)
            {
                ISystemCommand action;

                if (actions.TryGetValue(com, out action))
                {
                    action.Execute();
                }
                else
                {
                    Console.WriteLine("The command is not supported. Enter one of the valid commands.");
                    break;
                }
            }
        }
        Console.ReadLine();
    }
}

public interface ISystemCommand
{
    void Execute();
}

public class SystemCommandAttribute : Attribute
{
    public SystemCommandAttribute(string commandName)
    {
        CommandName = commandName;
    }

    public string CommandName { get; set; }
}

[SystemCommand("plus")]
public class Plus : ISystemCommand
{
    public void Execute()
    {
        Sum();
    }

    private void Sum()
    {
        // your existing Sum logic here
        Console.WriteLine("Sum called");
    }
}


[SystemCommand("?")]
public class ConsoleHelp : ISystemCommand
{
    public void Execute()
    {
        Helper();
    }

    private void Helper()
    {
        // your existing Helper logic here
        Console.WriteLine("Helper called");
    }
}

[SystemCommand("c")]
public class ExitCommand : ISystemCommand
{
    public void Execute()
    {
        Environment.Exit(0);
    }
}

Context

StackExchange Code Review Q#29664, answer score: 7

Revisions (0)

No revisions yet.