Mina
Mina

Reputation: 75

C# How to use lambda expression with dictionary's value which is a method

I'm creating a program which will execute a command after user input. Some commands I want to implement are: creating, reading a file, getting current working directory etc.

I created a dictionary which will store user input and corresponding command:

public static Dictionary<string, Action<string[]>> Commands { get; set; } = new Dictionary<string, Action<string[]>>()
        {

            {"pwd", PrintWorkingDirectory },
            {"create", CreateFile },
            {"print", ReadFile },
    };

Unfortunately I have issues with triggering the method:

public void Run()
    {
        Console.WriteLine("Welcome, type in command.");

        string input = null;

        do
        {
            Console.Write("> ");
            input = Console.ReadLine();
            Execute(input);
        } while (input != "exit");
    }

    public int Execute(string input)
    { 
        if(Commands.Keys.Contains(input))
        {
            var action = Commands.Values.FirstOrDefault(); //doesn't work, gives '{command} not found'
        }

        Console.WriteLine($"{input} not found");
        return 1;
    }

Also I noticed that this solution would not work with method which is not void, but returns something, as for example CreateFile.

public static string CreateFile(string path)
    {
        Console.WriteLine("Create a file");
        string userInput = Console.ReadLine();
        try
        {
            string[] file = userInput.Split(new char[] { ' ' }).Skip(1).ToArray(); 
            string newPath = Path.GetFullPath(Path.Combine(file));  
            using (FileStream stream = new FileStream(newPath, FileMode.Create, FileAccess.ReadWrite))
            {
                stream.Close();
            }
            using (StreamWriter sw = new StreamWriter(newPath))
            {
                Console.WriteLine("Please type the content.Press Enter to save.");
                sw.WriteLine(Console.ReadLine());
                sw.Close();
                Console.WriteLine("File {0} has been created", newPath);
            }

        }
        catch (Exception)
        {

            throw;
        }
        return path;
    }

public static void ReadFile(string[] args)
    {
        Console.WriteLine("Reading file");
        string userInput = Console.ReadLine();
        string[] file = userInput.Split(new char[] { ' ' }).Skip(1).ToArray(); 
        string newPath = Path.GetFullPath(Path.Combine(file));  
        string[] lines = File.ReadAllLines(newPath);

        foreach (string line in lines)
            Console.WriteLine(line);

    }

 public static void PrintWorkingDirectory(string[] args)
    {
        var currentDirectory = Directory.GetCurrentDirectory();
        Console.WriteLine(currentDirectory);            

    }

Could somebody advise me how to deal with these issues? Is it that this dictionary I created does not make much sense at all?

Upvotes: 1

Views: 1441

Answers (1)

Moerwald
Moerwald

Reputation: 11294

First problem: You're always fetching the first element of the dictionary and are not using the index operator to retrieve the correct value. Therefore change:

if(Commands.Keys.Contains(input))
{
    var action = Commands.Values.FirstOrDefault(); //doesn't work, gives '{command} not found'
}

to:

public int Execute(string input)
{
    if (Commands.Keys.Contains(input))
    {
        var action = Commands[input]; //doesn't work, gives '{command} not found'
        action?.Invoke(new string[] { });
    }
    else
    {
        Console.WriteLine($"{input} not found");
    }

    return 1;
}

Regarding to your second question about dictionary usage. I think it is ok to use a dictionary to map different commands based on a given key. The alternative would be switch or if constructs, which can be prevented in Object Oriented Programming.

Regarding to your question about string CreateFile(string path). Since C# is strongly typed language your dictionary can only contain objects of type Action<string[]>, so you can't use methods with another signature than that. One solution is to add another dictionary in the form of Dictionary<string,Func<string[], string>. As a result you'll get more and more dictionaries depending on your method signatures. From here on you should think to build to encapsulate your commands in an e.g. CommandInterpreter class, that could offer an API like that:

void Request(string cmdName, string[] cmdParameters);
string GetLastResult();
int GetLastCode();

Update:

Below code shows a possible object oriented solution (I've left out interfaces to make the code more compact):

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    public class Command<T> 
    {
        public string Name { get; }
        public T TheCommand { get; }

        public Command(string name, T theCommand)
        {
            Name = name;
            TheCommand = theCommand;
        }
    }

    public interface ICommandResult
    {
        void Ok(Action<ICommandResult> yes, Action<ICommandResult> no);
        int Code { get; }
        string Description { get; }
    }

    public abstract class CommandResult : ICommandResult
    {
        public int Code { get; }
        public string Description { get; }

        protected CommandResult(int code, string description)
        {
            Code = code;
            Description = description;
        }

        public abstract void Ok(Action<ICommandResult> yes, Action<ICommandResult> no);
    }

    public class NullCommandResult : CommandResult
    {
        public NullCommandResult() : base(-1, "null")
        {
        }

        public override void Ok(Action<ICommandResult> yes, Action<ICommandResult> no) => no?.Invoke(this);
    }
    public class SuccessCommandResult : CommandResult
    {
        public SuccessCommandResult(string description) : base(0, description)
        {
        }

        public override void Ok(Action<ICommandResult> yes, Action<ICommandResult> no) => yes?.Invoke(this);
    }

    public class CommandInterpreter
    {
        private Dictionary<string, Func<IEnumerable<string>, ICommandResult>> Commands = new Dictionary<string, Func<IEnumerable<string>, ICommandResult>>();
        public void RegisterCommand(Command<Func<IEnumerable<string>, ICommandResult>> cmd)
            => Commands.Add(cmd.Name, cmd.TheCommand);

        public ICommandResult RunCommand(string name, IEnumerable<string> parameters)
            => Commands.Where(kvp => kvp.Key.Equals(name))
                       .Select(kvp => kvp.Value)
                       .DefaultIfEmpty(strArr => new NullCommandResult())
                       .Single()
                       .Invoke(parameters);

    }
    class Program
    {
        private CommandInterpreter _cmdInterpreter;

        private Program()
        {
            _cmdInterpreter = new CommandInterpreter();
            _cmdInterpreter.RegisterCommand(new Command<Func<IEnumerable<string>, ICommandResult>>("pwd", PrintWorkingDirectory));
            _cmdInterpreter.RegisterCommand(new Command<Func<IEnumerable<string>, ICommandResult>>("create", CreateFile));
            _cmdInterpreter.RegisterCommand(new Command<Func<IEnumerable<string>, ICommandResult>>("print", ReadFile));
        }

        private static CommandResult ReadFile(IEnumerable<string> arg) => new SuccessCommandResult("File read");

        private static CommandResult CreateFile(IEnumerable<string> arg) => new SuccessCommandResult("File xyz created");

        private static CommandResult PrintWorkingDirectory(IEnumerable<string> arg) => new SuccessCommandResult("Printed something");

        static void Main() => new Program().Run();

        private void Run()
        {
            Console.WriteLine("Welcome, type in command.");

            string input;
            do
            {
                Console.Write("> ");
                input = Console.ReadLine();
                var cmdResult = _cmdInterpreter.RunCommand(input, Enumerable.Empty<string>());
                cmdResult.Ok(
                    r => Console.WriteLine($"Success: {cmdResult.Code}, {cmdResult.Description}"),
                    r => Console.WriteLine($"FAILED: {cmdResult.Code}, {cmdResult.Description}"));
            } while (input != "exit");
        }
    }
}

Output:

Welcome, type in command.
> pwd
Success: 0, Printed something
> create
Success: 0, File xyz created
> abc
FAILED: -1, null
>

You can just copy the code and play around with it.

Upvotes: 1

Related Questions