C# Как использовать лямбда-выражение со значением словаря, которое является методом - PullRequest
1 голос
/ 19 апреля 2020

Я создаю программу, которая будет выполнять команду после ввода пользователя. Вот некоторые команды, которые я хочу реализовать: создание, чтение файла, получение текущего рабочего каталога и т. Д. c.

Я создал словарь, в котором будут храниться пользовательский ввод и соответствующая команда:

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

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

К сожалению, у меня есть проблемы с запуском метода:

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;
    }

Также я заметил, что это решение не будет работать с методом, который не является пустым, но возвращает что-то, например, 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);            

    }

Может кто-нибудь посоветовать мне, как бороться с этими проблемами? Неужели этот словарь, который я создал, не имеет особого смысла?

1 Ответ

1 голос
/ 19 апреля 2020

Первая проблема: вы всегда выбираете первый элемент словаря и не используете оператор индекса для получения правильного значения. Поэтому измените:

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

на:

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;
}

Относительно вашего второго вопроса об использовании словаря. Я думаю, что можно использовать словарь для отображения различных команд на основе заданного ключа. Альтернативой могут быть конструкции switch или if, которые можно предотвратить в объектно-ориентированном программировании.

По вашему вопросу о string CreateFile(string path). Поскольку C# является строго типизированным языком, ваш словарь может содержать только объекты типа Action<string[]>, поэтому вы не можете использовать методы с другой сигнатурой. Одним из решений является добавление другого словаря в форме Dictionary<string,Func<string[], string>. В результате вы получите все больше и больше словарей в зависимости от сигнатур вашего метода. С этого момента вы должны подумать о сборке для инкапсуляции ваших команд, например, в класс CommandInterpreter, который может предложить такой API:

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

Обновление:

Ниже код показывает возможное объектно-ориентированное решение (я упустил интерфейсы, чтобы сделать код более компактным):

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");
        }
    }
}

Вывод:

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

Вы можете просто скопировать код и поиграй с этим.

...