为什么将委托传递给函数而不是在函数内部调用函数?

Why pass a delegate to a function instead of calling a function inside a function?

我试图理解委托的原因,但我发现很难看出将方法的引用传递给函数而不是在内部调用函数有什么用。

例如

class Program
{
    public static void Print(int num) 
        => Console.WriteLine(num);

    public static void Sum(int a, int b)
        => Print(a + b);
    
    public static void Sum(int a, int b, DelExample del) 
        => del(a + b);
    
    public delegate void DelExample(int num);

    static void Main(string[] args)
    {
        Sum(1, 2);

        DelExample del = Print;
        Sum(1,2, del);
    }
}

请注意 Sum 及其重载的工作方式相同。有什么理由可以说明为什么在这种情况下使用委托会比在内部调用函数更好?是否存在我需要委托的场景,因为使用函数的方式不同?我知道委托对于事件来说是必须的,但是这种场景什么时候有用呢?

如果你能给我一些关于它的例子(或其他有用的用途,如事件、回调、线程),或者一些资源来理解它,我也很感激,就像我五岁一样,因为我觉得我学到了很慢。提前致谢。

尝试创建一个名为 MathOperations 的委托,它接收参数 InfoEventArgs class。使用名为 OnAdd 的指针函数构建一个名为 MathClass 的 class,该函数接收发送者和委托指针函数的 InfoEventArgs 运行。您可以为其他数学运算添加其他委托方法。使用 getter 得到结果。 运行 一次调用委托方法并处理每个指针函数。

使用委托的主要原因是使用单个参数一次处理多个委托函数class。数学:(加、除、乘、最小二乘)

  public class InfoEventArgs : EventArgs
{
    public decimal Value1 { get; set; }
    public decimal Value2 { get; set; }
}
   public delegate void MathOperations(object sender, InfoEventArgs args);
   public class MathClass
{
    decimal _AddTwoNumbers = 0;
    decimal _DivideTwoNumbers = 0;

    public MathClass()
    {
        operations+=OnAdd;
        operations += OnDivide;

    }

    public event MathOperations operations;
    public void OnAdd(object sender, InfoEventArgs args)
    {
        _AddTwoNumbers = args.Value1 + args.Value2;
   }
   public void OnDivide(object sender, InfoEventArgs args)
    {
        if (args.Value2 == 0) { _DivideTwoNumbers = 0; }
        else { _DivideTwoNumbers = args.Value1 / args.Value2; }

    }
    public void Run(object sender, InfoEventArgs args)
    {
        operations(sender, args);
    }
    public decimal getAddTwoNumbers { get { return _AddTwoNumbers; } }
    public decimal getDivideTwoNumbers { get { return _DivideTwoNumbers; } }

}

    }
 [Fact]
        public void TestDelegate()
        {

             MathClass obj = new MathClass();

        InfoEventArgs args = new InfoEventArgs();
        args.Value1 = 6;
        args.Value2 = 3;
        obj.Run(this, args);

        _output.WriteLine($"{args.Value1} + {args.Value2} = {obj.getAddTwoNumbers}");
        _output.WriteLine($"{args.Value1} / {args.Value2} = {obj.getDivideTwoNumbers}");

        Assert.True(obj.getAddTwoNumbers == 9);
        Assert.True(obj.getDivideTwoNumbers == 2);

        }

好的,您知道“代表对于活动来说是必不可少的”,但我想知道您是否真的理解这种程度?

委托只是一种能够像传递数据一样传递方法的方法。这是必要的,因为有时某些代码的创建者(例如 Microsoft)不可能想象您将使用这些代码做的所有不同的事情。肯定有一些遗漏,一些他们无法编写的代码,因为他们只是不知道您将使用 C# 来做什么

Microsoft 创建了按钮 class。他们赋予它各种控制其外观的漂亮属性,并且允许您指定它在屏幕上的位置、大小、颜色和字体。但是,他们无法合理地知道单击它时您希望它做什么。你想让它做什么?打开一个文件?将一个人保存到数据库?关闭计算机?可能性是无限的..

所以在 Button 的代码中存在无法由编写 Button 的人填补的空白 class - 当您单击它时会发生什么?当您将鼠标悬停在上面时会发生什么?当..

时会发生什么

你可以为不同的差距想出很多场景,但只有一种方法可以弥补差距;让最终用户提供一些代码来做某事。作为 Button 的创建者,Microsoft 唯一需要的是您提供的那段代码具有某种特定的签名。假设他们想要一种可靠的方式来向您传递单击按钮的次数(单次或双次)、使用了哪个鼠标按钮(左键或右键)、鼠标指针在哪里(在坐标上)等

Microsoft 需要的是能够说“您可以传递任何您喜欢的方法作为单击按钮时将调用的方法,只要它需要 3 个参数;一个 int 表示次数,一个枚举表示它是鼠标左键还是右键,一个点表示鼠标光标所在的位置。每次用户单击时,我们都会用这 3 个东西调用您提供的方法

这就是您需要代表的原因;这样其他人就可以提供您将调用的方法,以弥合在您创建 class.

时“您不可能知道他们想要执行的操作”之间的差距

另一个例子:根据你不知道的标准做事,比如查找列表的元素

您正在编写 Person 对象的自定义集合,您想要根据某些条件搜索 Person。

foreach(var p in people)
  if(p.Firstname == "John")
    return p;

这样就可以了,但是有点不灵活。我们可以将它构建到我们的 PersonCollection 中,但那样它就只会知道如何搜索名字为 John 的人。我们可以将名称设为字符串参数,但它仍然不灵活,因为它仅按名字搜索。它最终会采用如下方法:

public void FindByFirstname(string n){
  foreach(var p in people)
    if(p.Firstname == n)
      return p;
}

Microsoft 不会做 John 的,因为它太有限了;几乎没有人会使用它。他们可能会使用 Firstname,因为它至少允许用户指定 Microsoft 不知道的名称。

如果微软可以为我们提供一种方法来指定 p.Firstname == "John" 位,这样我们也可以改变 Firstname 部分呢?我们可以提供一些体现我们想要的代码块,他们编写循环等,对于每个列表项,他们都提供 we 提供的代码块。如果 nugget return 为真,则他们 return 列表中的项目。他们不需要知道或关心我们的方法是做什么的;如果是星期二,它可能 return 正确,它只需要遵守规则:

  • 这是一种接受一个参数的方法,该参数是列表中的一个对象,因此它需要一个类型为 Person 的参数
  • 它 return 是一个布尔值,如果布尔值是真,我们 return 列表项

委托人可以执行此操作;委托使我们能够传递具有特定签名的方法,就像我们传递特定类型的数据一样。委托只是一个方法签名的声明,就像一个人是一个数据签名的声明(两个名字,一个年龄,一个社会保险号码..)。

delegate bool PersonSelector(Person p);

任何采用 Person 和 returns 布尔值的方法都可以充当此委托

于是Find方法诞生了。这是一种将另一个方法作为参数的方法。另一种方法必须接受一个人和 return 一个 bool

Person Find(PersonSelector perSel)

集合可以做调用委托方法的循环位;微软可以这样写:

foreach(var p in people)
  if(perSel(p))   //execute the delegate and use its return value 
    return p;

我们写下这段,因为我们知道我们要搜索约翰的名字:

bool IsCalledJohn(Person p){
  return p.Firstname == "John";
}

peopleCollection.Find(IsCalledJohn);

或者如果是星期二(我们不必使用 person 参数,我们只需要接受它和 return 一个布尔值):

bool IsTuesday(Person p){
  return DateTime.Now.DayOfWeek == DayOfWeek.Tuesday;
}


peopleCollection.Find(IsTuesday);

差距缩小了!微软可以继续编写他们的超快速搜索算法,而我们只是插入他们不知道的部分:搜索条件是什么。是的;很难看出“代表的意义是什么”如果你正在编写列表并使用列表 - 那里没有差距,因为你知道你'重新使用它。但是,一旦您将自己置于只是“编写工具但不使用它们”的人的位置上,您就会开始看到“用户必须提供这一点”的差距