字符串-函数字典C#,其中函数具有不同的参数。

14

基本上我正在尝试在c#中创建一个字符串到函数的字典,我已经看到过这样的实现:

Dictionary<string, Func<string, string>>

然而问题是我想要放到字典里的函数都有不同类型和数量的参数。那么我该如何创建一个能够实现这一点的字典呢?

亚当


你的问题有点不清楚。你是想读取一个字符串还是创建一个字符串? - Frank Pytel
9
如果你的“字符串到函数字典”中各个函数拥有不同数量和类型的参数,那么你可能正在使用错误的解决方案。让我来翻译一下接下来的问题:“是什么原始问题让你认为‘我知道了,我要使用一个字典!’?” - CodeCaster
2
你试图解决的是什么原始问题?假设你确实做到了这一点,你会如何使用这个词典 - 你将如何调用任何一个函数? - Atul Sikaria
基本上我正在制作一个基于文本的RPG游戏,因此我实际上是使用字典将字符串命令映射到不同的函数。 - RoboPython
6
你打算如何使用这本词典?调用带有任意数量未知参数的函数有些困难... - Alexei Levenkov
你可能有一些工作流来调用字典中的那些方法。因此,你可以使用MONAD来解决你的问题。http://mikhail.io/2016/01/monads-explained-in-csharp/ - Paulo Prestes
9个回答

12

您可以定义自己的委托,使用 params string[] 参数,像这样:

delegate TOut ParamsFunc<TIn, TOut>(params TIn[] args);

然后像这样声明您的字典:

Dictionary<string, ParamsFunc<string, string>> functions;

所以,您可以这样使用:

public static string Concat(string[] args)
{
    return string.Concat(args);
}

var functions = new Dictionary<string, ParamsFunc<string, string>>();
functions.Add("concat", Concat);

var concat = functions["concat"];

Console.WriteLine(concat());                                //Output: ""
Console.WriteLine(concat("A"));                             //Output: "A"
Console.WriteLine(concat("A", "B"));                        //Output: "AB"
Console.WriteLine(concat(new string[] { "A", "B", "C" }));  //Output: "ABC"

请注意,即使您只需要一个string参数,仍然需要使用string[]参数声明您的方法。

另一方面,它可以使用params样式调用(如concat()concat("A", "B"))。


5
您可以使用Dictionary<string, Delegate>。要调用存储在Delegate中的函数,请使用DynamicInvoke()方法。

4

我会选择ExpandoObject,因为它专门用来支持动态语言(例如IronPython等),让CLR支持这些语言。

static void Main()
{
    dynamic expando = new ExpandoObject();
    expando.Do = new Func<string>(MyFunc);
    expando.Do2 = new Func<string, string>(MyFunc2);

    Console.WriteLine(expando.Do());
    Console.WriteLine(expando.Do2("args"));
}

static string MyFunc()
{
    return "Do some awesome stuff";
}

static string MyFunc2(string arg)
{
    return "Do some awesome stuff with " + arg;
}

4
首先,如果您必须使用方法字典,则请实现具有相同签名的方法!(或非常相似,差异尽可能小)如果不能实现,请使用Lambda表达式转换方法签名。

Lambda表达式

    static string Method1(string a)
    {
        return a;
    }

    static void Method2(string a, string b)
    {
        Console.WriteLine(a + b);
    }

    static string Method3(string a, string b, int x)
    {
        return String.Format("a:{0} b:{1} x:{2}", a, b, x);
    }

    static int Method4(int x)
    {
        return x;
    }

    static void Main(string[] args)
    {
        var methods = new Dictionary<string, Func<int, string, string, string, string>>()
        {
            { "method1", (x, a, b, c) => Method1(a) },
            { "method2", (x, a, b, c) => { Method2(a, b); return ""; } },
            { "method3", (x, a, b, c) => Method3(a, b, x) },
            { "method4", (x, a, b, c) => Method4(x).ToString() },
        };
        foreach (var key in methods.Keys)
            Console.WriteLine(key + ": " + methods[key](1, "a", "b", "c"));
        Console.ReadKey();
    }

正如您所看到的,为了使您的字典拥有所有可能的参数,您必须保留方法签名。这很丑陋! 但这将起作用,您不必关心哪个方法在哪个字符串后面。它们都将使用相同的参数。但是,在调用它们时,您必须非常小心,因为由于要传递的参数数量很多,您很容易在lambda表达式中调用它们时犯错误。


结构/字典参数传递

    struct method_parameters
    {
        public string a;
        public string b;
        public int x;
    }

    static string Method1(method_parameters parameters)
    {
        return parameters.a;
    }

    static void Method2(method_parameters parameters)
    {
        Console.WriteLine(parameters.a + parameters.b);
    }

    static string Method3(method_parameters parameters)
    {
        return String.Format("a:{0} b:{1} x:{2}",
            parameters.a, parameters.b, parameters.x);
    }

    static int Method4(method_parameters parameters)
    {
        return parameters.x;
    }

    static void Main(string[] args)
    {
        method_parameters parameters = new method_parameters()
        {
            a = "a",
            b = "b",
            x = 1
        };
        var methods = new Dictionary<string, Func<method_parameters, string>>()
        {
            { "method1", Method1 },
            { "method2", (param) => { Method2(param); return ""; } },
            { "method3", Method3 },
            { "method4", (param) => Method4(param).ToString() },
        };
        foreach (var key in methods.Keys)
            Console.WriteLine(key + ": " + methods[key](parameters));
        Console.ReadKey();
    }

这种方法更易于维护,因为如果您需要添加/更改参数,则不必更新每个方法和字典条目。您只需要根据所选择的参数存储方式修改您的结构/类/字典即可。但是,在调用方法之前,您需要首先更新您的结构。必要时还需要清除它!


3

考虑到你的使用场景是基于文本的RPG游戏,需要使用一组带参数的命令,也许你应该将所有的命令都制作成方法,这些方法都具有以下签名:

MethodName(string[] args)

使用字典查找

new Dictionary><string, Action<string[]>>

那样会更方便使用字典并调用相关方法。

2
一种简单但不好的解决方案可能是这样的:
private void methodDictionary()
{
    var infos = new Dictionary<string, MethodInfo>();
    infos.Add("a", this.GetType().GetMethod("a"));
    infos.Add("b", this.GetType().GetMethod("b"));

    MethodInfo a = infos["a"];
    a.Invoke(this, new[] { "a1", "b1" });

    MethodInfo b = infos["b"];
    b.Invoke(this, new object[] { 10, "b1", 2.056 });
}

public void a(string a, string b)
{
    Console.WriteLine(a);
    Console.WriteLine(b);
}

public void b(int a, string b, double c)
{
    Console.WriteLine(a);
    Console.WriteLine(b);
    Console.WriteLine(c);
}

1
你仍然无法向该字典添加具有多个参数的方法。 - CodeCaster
1
"不同数量的参数" - Dictionary<string, object> 可以工作,但那甚至更糟... - James Thorpe

1
创建一个包装类来存储函数委托作为对象,是解决此问题的一种可能方法。然后,您可以使用通用方法来添加和检索与您的签名匹配的函数。
以下代码示例展示了一个非常基本的实现:
class FunctionDictionary
{
    /// <summary>
    /// Internal dictionary that will store the function delegates as Object.
    /// </summary>
    private Dictionary<string, Object> m_Map = new Dictionary<string, object>();

    /// <summary>
    /// Add method to dictionary for specified key. Encapsulated method has no parameters.
    /// </summary>
    public void Add<TResult>(string key, Func<TResult> function) 
    {
        m_Map.Add(key, function);
    }

    /// <summary>
    /// Get method for specified key. Encapsulated method has no parameters.
    /// </summary>
    public Func<TResult> Function<TResult>(string key)
    {
        return (Func<TResult>)m_Map[key];
    }

    /// <summary>
    /// Add method to dictionary for specified key. Encapsulated method has one parameters.
    /// </summary>
    public void Add<T1, TResult>(string key, Func<T1, TResult> function)
    {
        m_Map.Add(key, function);
    }

    /// <summary>
    /// Get method for specified key. Encapsulated method has one parameters.
    /// </summary>
    public Func<T, TResult> Function<T, TResult>(string key)
    {
        return (Func<T, TResult>)m_Map[key];
    }


    public void Add<T1, T2, TResult>(string key, Func<T1, T2, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, TResult>(string key, Func<T1, T2, T3, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, TResult>(string key, Func<T1, T2, T3, T4, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, TResult>(string key, Func<T1, T2, T3, T4, T5, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult> function)
    {
        m_Map.Add(key, function);
    }
    public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>(string key, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult> function)
    {
        m_Map.Add(key, function);
    }


    public Object this[string key]
    {
        get
        {
            return m_Map[key];
        }
    }

    public Func<T1, T2, TResult> Function<T1, T2, TResult>(string key)
    {
        return (Func<T1, T2, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, TResult> Function<T1, T2, T3, TResult>(string key)
    {
        return (Func<T1, T2, T3, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, TResult> Function<T1, T2, T3, T4, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, TResult> Function<T1, T2, T3, T4, T5, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, TResult> Function<T1, T2, T3, T4, T5, T6, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, TResult> Function<T1, T2, T3, T4, T5, T6, T7, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>)m_Map[key];
    }
    public Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult> Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>(string key)
    {
        return (Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>)m_Map[key];
    }

}

以下代码示例演示了用法:
class FunctionMapUsage
{
    private FunctionDictionary functions = new FunctionDictionary();
    public string FunctionA()
    {
        return "A";
    }

    public string FunctionB(int value)
    {
        return value.ToString();
    }

    public int FunctionC(string str1, string str2)
    {
        return str1.Length + str2.Length;
    }

    public void CreateFunctionMap()
    {
        functions.Add<string>("A", FunctionA);                      // Add Function A to map
        functions.Add<int, string>("B", FunctionB);                 // Add Function B to map
        functions.Add<string, string, int>("C", FunctionC);         // Add Function C to map
    }

    public void CallFunctions()
    {
        var functionA = functions.Function<string>("A");                // Get Function A
        var functionB = functions.Function<int, string>("B");           // Get Function B
        var functionC = functions.Function<string, string, int>("C");   // Get Function C

        string resultA = functionA();
        string resultB = functionB(123);
        int resultC = functionC("parameter 1", "parameter 2");
    }
}

CreateFunctionMap方法只是将新函数添加到字典中。 CallFunctions方法展示了如何从字典中提取和执行函数。

1

有几种方法可以实现这个目标,但是没有很好的答案。如果您能够做到这一点,您仍然需要更多的逻辑来调用函数,除非您的函数只有不同数量的字符串。然后每个函数都可以处理自己的参数。

话虽如此,您可以这样做:

    public delegate string DelegateAction(params string[] args);
    public Dictionary<string, DelegateAction> Actions = new Dictionary<string, DelegateAction>();

    public void InitializeDictionary()
    {
      Actions.Add("walk",Move);
    }


    public string Move(params string[] args)
    {
      if (args.Length > 0)
      {
        if (!string.IsNullOrWhiteSpace(args[0]))
        {
          switch (args[0].ToLower())
          {
            case "forward":
              return "You move forward at a leisurely pace";
            case "right":
            case "left":
            case "backward":
              throw new NotImplementedException("Still need to set these up");
            default:
              return "You need to specify a valid direction (forward,backward,right,left).";
          }
        }
      }
      return "You need to specify a direction.";
    }

    public string ProcessAction(string action, params string[] args)
    {
      return Actions[action.ToLower()].Invoke(args);
    }

如果你要这样做,需要注意密钥区分大小写,因此你需要使用小写/ToLower()或大写/ToUpper()。你可以以多种方式处理其他参数,其中一些可以使用其他不区分大小写的匹配方式,但在此示例中使用switch-case时,大小写必须匹配。

祝你好运!


0
解决这个问题的一种方法是引入一个参数容器/命令类,其中包含超集函数参数,包括动作类型本身(上述问题中字典中的键):
public class ActionCommand
{
    public ActionType ActionType { get; set; } // enum instead of string
    public int Distance { get; set; }
    public DirectionType DirectionType { get; set; }
    // More properties as needed
}

然后,针对每个ActionType实现一个strategy类:

public interface IActionHandlerStrategy
{
    bool AppliesTo(ActionCommand actionCommand);
    string Apply(ActionCommand actionCommand);
}

public class WalkActionHandlerStrategy : IActionHandlerStrategy
{
    public bool AppliesTo(ActionCommand actionCommand)
    {
        return ActionCommand.ActionType == ActionType.Walk;
    }

    public ActionResult Apply(ActionCommand actionCommand)
    {
        // Do something here and return the result of Walk action
        return new ActionResult(...); // Container for results
    }
}

public class JumpActionHandlerStrategy : IActionHandlerStrategy
{
    public bool AppliesTo(ActionCommand actionCommand)
    {
        return ActionCommand.ActionType == ActionType.Jump;
    }

    public ActionResult Apply(ActionCommand actionCommand)
    {
        // Do something here and return the result of Jump action
        return new ActionResult(...); // Container for results
    }
}

根据需要添加更多的策略,然后实现一个应用它们的类:

public class ActionHandler
{
    // Register strategies with the handler; typically done with DI
    protected IList<IActionHandlerStrategy> ActionHandlerStrategies = new List<IActionHandlerStrategy>
        {
            new JumpActionHandlerStrategy(),
            new WalkActionHandlerStrategy(),
            // More strategies here...
        };

    public ActionResult Handle(ActionCommand actionCommand)
    {
        var actionHandlerStrategy = ActionHandlerStrategies.FirstOrDefault(ahs => ahs.AppliesTo(actionCommand);
        if (actionHandlerStrategy == null)
        {
            throw new Exception($"No strategy found for action type {actionCommand.ActionType}");
        }
        return actionHandlerStrategy.Apply(actionCommand);
    }
}

最终结果应该允许相对简单地添加新的操作,而不会对现有逻辑产生太大影响。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接