我似乎永远不明白我们为什么需要代表? 我知道它们是包含方法引用的不可变引用类型,但为什么我们不能直接调用该方法,而不是通过委托调用它?
由于
答案 0 :(得分:59)
简单回答:需要执行操作的代码不知道写入时要调用的方法。如果你在编译时知道要调用哪种方法,你只能直接调用该方法,对吧?因此,如果您想要抽象出“在适当的时间执行操作X”的想法,您需要对操作进行一些表示,以便调用操作的方法不需要提前知道确切的实现。
例如:
Enumerable.Select
无法知道您要使用的投影,除非您告诉它Button
的作者不知道你希望用户点击它时的操作它可能会帮助您将委托视为单方法接口,但使用大量语言语法使其易于使用,并且支持异步执行和多播。
答案 1 :(得分:33)
当然,您可以直接在对象上调用方法,但请考虑以下方案:
Select
方法提供大量实现。答案 2 :(得分:17)
因为您可能已经编写了方法,或者您已经设计了您的类,其用户可以决定用户希望您的类使用哪种方法(该用户编写)执行。
它们还使某些设计更干净(例如,代替您调用不同方法的switch语句,调用传入的委托)并且更容易理解并允许扩展代码而不更改它(想想OCP)。 / p>
代表也是事件系统的基础 - 在没有代表的情况下编写和注册事件处理程序会比使用它们更难。
查看Linq中的不同Action
和Func
代表 - 如果没有它们,它几乎没有用。
话虽如此,没有人强迫你使用代表。
答案 3 :(得分:8)
答案 4 :(得分:4)
代表们可以在没有他们的情况下完成任何事情,但代表们提供了更清晰的方法。如果没有委托,则必须为包含函数Invoke(适当的参数)的每个可能的函数签名定义接口或抽象基类,并为每个可由伪委托调用的函数定义一个类。该类将继承函数签名的适当接口,包含对包含它应该表示的函数的类的引用,以及实现Invoke(适当的参数)的方法,该方法将调用它所持有的类中的相应函数一个参考。如果类Foo有两个方法Foo1和Foo2,两个方法都采用一个参数,两个都可以被伪委托调用,那么就会创建两个额外的类,每个方法一个。
如果没有编译器支持这种技术,源代码必须非常令人发指。但是,如果编译器可以自动生成正确的嵌套类,那么事情可能非常简洁。伪委托的调度速度通常可能比传统委托慢,但如果伪委托是接口而不是抽象基类,那么只需要为给定签名的一个方法创建伪委托的类就可以了。实现适当的伪委托接口本身;然后可以将类实例传递给期望该签名的伪委托的任何代码,从而避免创建额外对象的任何需要。此外,虽然使用伪委托时所需的类数量大于使用“真实”委托时所需的类数,但每个伪委托只需要保存一个对象实例。
答案 5 :(得分:3)
考虑C / C ++函数指针,以及如何将javascript事件处理函数视为“数据”并传递它们。在Delphi语言中也有程序类型。 在幕后,C#delegate和lambda表达式以及所有这些基本相同的想法:代码作为数据。这构成了函数式编程的基础。
答案 6 :(得分: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;
}
答案 7 :(得分:0)
您可以将它们视为与C / C ++中的函数指针类似的构造。但它们不仅仅是C#中的。 Details