我是否以有用的方式使用 C# 委托?

Am I using C# delegates in a useful way?

我正在使用 C# 中的委托做一个简单的练习。基本前提是它只是简单地执行加法和减法等计算。

public class Calculator
{
    public static int Add(int num1, int num2)
    {
        return num1 + num2;
    }

    public static int Multiply(int num1, int num2)
    {
        return num1 * num2; 
    }

    public static int Subtract(int num1, int num2)
    {
        return num1 - num2;
    }
}

并且我创建了一个委托,它将在另一个 class 中调用这些函数。

public delegate int CalculatorDelegate(int num1, int num2);

实现 class 如下所示:

public class Calculation
{
    public int PerformCalculation(int choice, int number1, int number2)
    {
        CalculatorDelegate cd;
        if (choice == 1)
        {
            cd = Calculator.Add;
            return cd(number1, number2);
        }
        else if (choice == 2)
        {
            cd = Calculator.Subtract;
            return cd(number1, number2);
        }
        else if (choice == 3)
        {
            cd = Calculator.Multiply;
            return cd(number1, number2);
        }
        else
        {
            cd = Calculator.Add;
            return cd(number1, number2);
        }
    }

}

让我对委托感到困惑的是为什么不直接执行以下操作?

if (choice == 1)
{
    return Calculator.Add(number1,number2);
}

我认为我的困惑是围绕着这样一个事实:如果这些方法已经是静态的,那么即使对它们进行委托又有什么意义呢。我可以简单地将它们称为静态方法。如果我尝试使这些方法成为非静态方法,那么我不能让我的委托在另一个 class 中指向它们,而不创建另一个 class 的某些对象。

我对这个例子及其完成的事情完全感兴趣,也许有人可以给我一些见解,看看我是否在远程使用这个接近它应该的样子?

委托是类型。引用函数而不是其他值的类型。

委托将函数作为参数传递给另一个函数。或者用作通用 class 的类型 - 实际上主要是集合。有 many 必须发明的裸指针替代品之一,因此 .NET 可以使您不必处理裸指针。

然而,他们也是这样一个案例,其中置换结果 更好 相当可观。例如,用 Dictionary<caseLabel, delegateThatTakesAArgument> 替换 switch/case 是很有可能的。如果我在 Native C++ 中给你一个 int 指针。您无法判断这是我通过引用提交的单个变量、数组的开头还是 returns int 的函数。有了所有这些指针替换,你知道。

对于这种特定情况,这些功能本身就有些矫枉过正了。代表们更是大材小用。 "PerformCalculation" 函数听起来应该是控制台应用程序的用户输入解释部分,而不是函数。

然而,没有很多好的、明确的 示例供 Delegate 使用。因此,为了学习,它可能会正常工作。通常情况下,您必须在具有硬编码操作的 switch/case 和委托之间进行判断调用。

然而,事件是我能想到的代表的罕见明确案例之一。如果你为了其他目的而学习它们,那么为了活动而学习它们。

我创建了一个 Repl,演示了使用基本委托的 Worker 模式(Actions,一种内置于标准库中的委托类型,不接受任何参数,returns 什么也没有)。

你可以看看here

ETA:代表最常见的用例是 asynchronous/threaded 编程。

线程需要委托给 运行,因为它们事先不知道代码是 运行,它们只是将它作为参数携带到新创建的执行线程中,否则是不可能的创建线程(好吧,没有比 C# 提供的更低级别的访问权限)

查看 Threading API

上的 MSDN 文档

同样,在异步编程中,您通常需要一个回调(在 C# 中是一个委托)来推迟代码的执行,直到满足预先存在的条件。 (例如 运行 循环,一种常见的线程替代方法,它使用单个线程逐步执行多个任务 "concurrently")

您可以在示例中使用 delegates 的一种方法是从选择到 CalculatorDelegate 进行映射,然后您只需查找所需的计算并执行它:

static List<CalculatorDelegate> ChoiceMap = new List<CalculatorDelegate> { Calculator.Add, Calculator.Subtract, Calculator.Multiply };

public class Calculation {
    public int PerformCalculation(int choice, int number1, int number2) =>
        ChoiceMap[choice-1](number1, number2);
}

但是由于委托是从 lambda 自动转换的(CalculatorDelegate 本质上是 Func<int,int,int>),您不再需要 Calculator class:

static List<CalculatorDelegate> ChoiceMap = new List<CalculatorDelegate> {
    (n1, n2) => n1+n2,
    (n1, n2) => n1-n2,
    (n1, n2) => n1*n2
};

请注意,每个 lambda 中 n1n2 的类型是从 CalculatorDelegate 推断出来的。现在向 PerformCalculation 添加新的操作只是向 ChoiceMap.

添加一个新的 lambda 的问题

希望您能看到如何为 ChoiceMap 使用更复杂的类型,例如 Dictionary<string,Func<int,int,int>>,其中每个键都是表示运算符的字符串(例如 "+")和该值是要执行的操作,这使得映射到简单的计算器变得容易。

我在命令行开关管理助手中使用了类似的 Dictionary,其中每个条目都有命令名称、参数数量、要调用的委托(方法)(简单时可能是 lambda足够),命令分组和一些帮助文本,其中帮助输出是从相同的 Dictionary.

自动生成的