为什么我们需要C#委托?

58
我不明白为什么我们需要使用委托? 我知道它们是持有方法引用的不可变引用类型,但为什么我们不能直接调用方法而非通过委托来调用呢?
谢谢。

抱歉,我是指不可变的。 - InfoLearner
可能是Where do I use delegates?的重复问题。 - nawfal
8个回答

62
简单来说,需要执行某个操作的代码在编写时并不知道要调用哪个方法。只有在编译时就知道要调用的方法才能直接调用它,正确吗?因此,如果您想抽象出“在适当的时间执行动作X”的概念,您需要某种表示该动作的方式,这样调用该动作的方法不需要提前知道确切的实现。
例如:
- 在LINQ中,Enumerable.Select无法知道您想使用哪个投影。 - Button的作者不知道用户在单击按钮时想要执行什么操作。 - 如果新线程只做一件事情,那将会很无聊...
你可以将委托视为单方法接口,但具有许多语言语法使它们易于使用,并且对于异步执行和多播具有奇特的支持。

在这种情况下我们不能使用策略模式吗? - Atish Kumar Dipongkor
1
@AtishDipongkor:有效的委托实际上是单方法策略模式的一种实现方式...但更加灵活。 (该策略可以使用私有方法、lambda表达式等来实现。) - Jon Skeet

36

当然,您可以直接在对象上调用方法,但考虑以下情况:

  1. 您想使用单个委托调用一系列方法,而不必编写大量的方法调用。
  2. 您想优雅地实现基于事件的系统。
  3. 您想调用两个签名相同但驻留在不同类中的方法。
  4. 您想将方法作为参数传递。
  5. 您不想编写许多多态代码(如LINQ),可以为 Select 方法提供很多实现。

1
感谢您的回复。1-我不能写一个方法,然后调用其他方法吗?例如:CallAllMethods(); 2.为什么我们想要将方法作为参数传递?您能否给出一个可能有帮助的场景?再次感谢。 - InfoLearner
@user520692:当然,你可以这样做,而且这样做没有任何害处,但是你可以将此委托传递给另一个类,并根据某些自定义条件调用该委托。对于您的第二个问题也是如此,就像Jon上面指定的那样,Enumarable.Select不知道如何选择记录,因此您可以通过传递一个委托来选择记录,该委托指向一个方法,在最后,没有人强迫您在任何情况下使用委托。 - TalentTuner
1
当然,你可以编写一个调用其他方法的方法,但是如果你想编写一个处理来自外部代码回调的黑盒子代码,并强制执行类型安全性呢?委托提供了一种定义良好的机制,具有编译器和运行时支持,而不是强制人们实现自己的机制。 - Chris

18
因为您可能尚未编写该方法,或者您已经以这种方式设计了您的类,使得使用它的用户可以决定用户想要让您的类执行哪个方法(该用户编写)。
它们还可以使某些设计更加简洁(例如,调用不同方法的 switch 语句),更易于理解,并允许扩展您的代码而不更改它(考虑 OCP)。
委托也是事件系统的基础-在没有委托的情况下编写和注册事件处理程序将比使用委托困难得多。
请参见 Linq 中的不同 Action 和 Func 委托-没有它们,它几乎无法成为有用的工具。
话虽如此,没有人强迫您使用委托。

8
  1. 代理支持事件
  2. 委托使您的程序能够在编译时不需要精确知道这些方法的情况下执行方法

似乎是从这里盗取的:http://www.techmcq.com/question/input/output/420 - Disillusioned

4
任何可以使用委托完成的任务,都可以不用委托来完成,但是使用委托会让代码更加简洁。如果没有委托,将需要为每个可能的函数签名定义一个接口或抽象基类,其中包含Invoke方法(适当的参数),并定义一个类来实现伪委托可调用的每个函数。该类将继承指定签名的接口,包含对包含它所代表的函数的类的引用,并实现Invoke方法(适当的参数),以便在其持有引用的类中调用适当的功能。例如,如果类Foo有两个方法Foo1和Foo2,均接受单个参数,同时可以通过伪委托进行调用,则将创建两个额外的类,用于代表每个方法。
如果没有编译器支持这种技术,源代码将会非常丑陋。但是,如果编译器可以自动生成正确的嵌套类,则代码可以变得更加简洁。虽然伪委托的分发速度可能通常会比传统委托慢,但如果伪委托是接口而不是抽象基类,则只需要为给定签名的一个方法制作伪委托的类可以自己实现适当的伪委托接口;然后可以将类实例传递给期望该签名的伪委托的任何代码,而无需创建额外的对象。此外,使用伪委托时需要的类的数量将比使用“真实”委托时多,但每个伪委托只需要持有一个对象实例。

如果您能在这里分享一些样例,那将非常酷。 - NoWar

3

想一想C/C++函数指针,以及如何将javascript事件处理函数视为“数据”并传递它们。在Delphi语言中也有过程类型。 在幕后,C#代理和lambda表达式,以及所有这些东西本质上都是相同的思想:代码作为数据。这构成了函数式编程的基础。


1
你要求一个关于为什么要将函数作为参数传递的例子,我有一个完美的例子,希望它能帮助你理解。虽然这个例子比较学术,但是它展示了一种用法。假设你有一个ListResults()方法和一个getFromCache()方法。你可以通过将任何方法传递给getCache,而不必进行很多检查(例如缓存是否为空等),然后只在getFromCache方法内部调用它,以避免重复代码:

_cacher.GetFromCache(delegate { return ListResults(); }, "ListResults");

public IEnumerable<T> GetFromCache(MethodForCache item, string key, int minutesToCache = 5)
    {
        var cache = _cacheProvider.GetCachedItem(key);
        //you could even have a UseCache bool here for central control
        if (cache == null)
        {
            //you could put timings, logging etc. here instead of all over your code
            cache = item.Invoke();
            _cacheProvider.AddCachedItem(cache, key, minutesToCache);
        }            

        return cache;
    }

请问您能否写出其他写得不好的代码,以便我们可以看到为什么需要按照您所建议的方式进行操作? - NoWar
这个例子太糟糕了,伙计。 - Riegardt Steyn
当然可以,但这确实说明了委托是如何工作的。那不就是问题吗? - GarethReid

0

你可以将它们视为类似于C/C++中函数指针的构造。但在C#中,它们不仅如此。详情


坏建议啊... 想象一下,如果他不知道 C/C++ 中的“函数指针”。呵呵呵呵呵 - NoWar

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