【问题标题】:How do I pass variable arguments to a function that calls specific functions based on the number of arguments?如何将可变参数传递给根据参数数量调用特定函数的函数?
【发布时间】:2018-06-18 18:59:27
【问题描述】:

这是How do I pass a function pointer delegate for a different class in c#的后续问题

我有课

public class ClassA
{
    public void Foo()
    {
        Console.WriteLine("Foo()");
    }
    public void Foo(int x, int y)
    {
        Console.WriteLine("Foo(" + x.ToString() + ", " + y.ToString()  + ")" );
    }
    public void Foo(int x, int y, int z)
    {
        Console.WriteLine("Foo(" + x.ToString() + ", " + y.ToString() + ", " + z.ToString() + ")" );
    }
}

在另一种方法中,我想像这样调用classA的类函数:

ClassA obj = new ClassA();
TakesFun(obj.Foo);
TakesFun(obj.Foo, 1, 2);
TakesFun(obj.Foo, 1, 2, 3);

TakesFun 的语法应该是什么?我想让它成为通用的,以接受许多/任何/无参数并根据参数的数量调用适当的函数?

显然下面的函数语法是错误的,但我正在寻找一个类似的函数。

public static void TakesFun<TParam>(Action<TParam[]> action, params TParam[] paramList)
{
    Console.WriteLine("TakesFun");
    action(paramList);
}

EDIT1:ClassA Foo 函数只是一个示例。我想调用可以接受任何类型参数的更复杂的函数。 例如:

public void CleanList(List<int> l)
{
    l.Clear();
}

【问题讨论】:

  • 为什么要这么复杂??只需将属性 x、y 和 z 作为可空整数添加到 ClassA 中,如果 x、y 和 z 为空,则调用 Foo(),如果 z 为空,则调用 Foo(x, y),如果 z 不为空,则调用 Foo(x , y, z)
  • 是的,这似乎是一个非常糟糕的设计。可选参数是一个东西,使用它们
  • 你能说明一下如何吗?我问这个是因为我是 C# 新手。
  • 我正在为非 WPF 和非 GUI 应用程序编写一个等效的线程调度程序,我必须有一个 BeginInvoke 的函数语法,该语法以与 TakesFun 类似的行完成,可以采用动作和参数,然后在稍后运行该函数。我已使用 EDIT1 更新了列表,显示了我想要运行的更多类型的函数。我不能对 ClassA 函数使用可选参数。有关背景的更多详细信息,请参见原帖中的链接。
  • 嗯?使用回调/委托。为什么要把事情复杂化……简单是最难掌握的工程原则。

标签: c# parameters


【解决方案1】:

在另一种方法中,我想像这样调用 classA 的类函数: TakesFun(obj.Foo);

您会注意到TakesFun 是一个采用Action 的单一方法,而不是重载方法的集合。

你不能总是得到你想要的,你必须学会​​忍受失望。 C# 不支持你想要的功能。

您正在将obj.Foo方法组形式 转换为委托形式,这需要C# 通过比较来执行重载解析 TakesFun 的形式参数列表中的委托与名为 Foo 的方法集合,然后选择最佳的一个。

由于在编译时没有“最好的”,这将失败。

寻找另一种方法来解决您的问题。 你的问题听起来很像我们所说的“XY 问题”。也就是说,你有一些真正的问题,你有一个疯狂的想法来解决它,但它永远不会奏效,而现在你'再问一个关于你疯狂想法的问题。问一个关于真正问题的问题。几乎可以肯定,有比这更好的解决方法。

现在,如果我们放宽您的问题的一些限制,那么我们就可以做到。特别是,如果我们放宽只有一个TakesFun 并且它是可变参数的约束,那么我们就可以做到。我注意到TakesFun实际上是函数应用操作:

static void Apply(Action action) => action();
static void Apply<A>(Action<A> action, A a) => action(a);
static void Apply<A, B>(Action<A, B> action, A a, B b) => action(a, b);
static void Apply<A, B, C>(Action<A, B, C> action, A a, B b, C c) => action(a, b, c);
... and so on, as many as you need

现在,重载解析的魔力解决了您的问题:

Apply(obj.Foo, 1, 2, 3); // Works fine

重载解析推导出Apply 表示Apply&lt;int, int, int&gt;ActionAction&lt;int, int, int&gt;Foo 表示需要三个整数的那个。

C# 类型推断非常好,但您必须在我们为语言设计的范围内使用它。


更新:根据您的 cmets,听起来您正在尝试重新发明任务并行库。我会调查 TPL,看看它是否已经满足您的需求。

在 C# 中,我们将将来执行的工作单元的概念表示为Task;有一个庞大的库和内置的语言支持,用于从任务中组合工作流,将它们的延续安排到各种线程等等。 使用这项工作,而不是试图自己发明它

例如,如果您想创建代表未来工作的未开始任务,您可以这样做:

static Task TaskFactory(Action action) => new Task(action);
static Task TaskFactory<A>(Action<A> action, A a) => new Task(()=>action(a));
static Task TaskFactory<A, B>(Action<A, B> action, A a, B b) => new Task(()=>action(a, b));

等等。现在你可以这样做了:

Task t = TaskFactory(obj.Foo, 1, 2);

并取回t启动时将执行该操作。然后,您可以说出您希望在任务完成后发生的事情:

t.ContinueWith(task => Console.WriteLine("Task complete!"));

然后启动它:

t.Start();

有一些机制可以创建已经启动并在工作线程上运行的任务。有一些机制可以调度任务或其完成以在特定线程上运行。等等;这是一个巨大的话题。如果您对此有任何疑问,请提出另一个问题。

【讨论】:

  • @sgowd:我建议你先,更好地解释你的问题。其次,我已经描述了对我的答案的补充,您可以如何通过放宽您对问题不必要地施加的约束在 C# 中做到这一点。
  • @sgowd:我不清楚的是你为什么要这样做? C# 中已经有一个函数应用运算符。当你可以简单地写obj.Foo(1, 2, 3)时,你为什么要写TakesFun(obj.Foo, 1, 2, 3),它更短更容易?
  • @sgowd:听起来你正在尝试重新发明 async-await 和任务并行库,非常糟糕。 C# 已经有一个机制可以精确地内置在语言。如果您想将异步工作分派到线程上,那么首先,我会调查标准异步工作线程上下文是否已经是您想要的,如果不是,那么请研究如何编写自己的上下文。
  • @Eric Lippert,我将在 async-await 上查找。什么是函数应用算子?
  • @Eric Lippert,感谢您并让我意识到 XY 问题。鉴于大多数人试图解决问题 X 或为问题 Y 提供替代解决方案,我意识到提问者陈述完整问题很重要。我在工作中也经常遇到这个问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-28
  • 2019-01-17
  • 2015-01-05
  • 2015-09-23
  • 1970-01-01
  • 2014-10-20
  • 2017-09-28
相关资源
最近更新 更多