when & why to use delegates?
我对C语言比较陌生,我想知道何时适当地使用代理。它们在事件声明中被广泛使用,但是我应该在什么时候在自己的代码中使用它们,为什么它们有用?为什么不用别的东西?
我还想知道什么时候我必须使用代理,我没有其他选择。
谢谢你的帮助!
编辑:我想我在这里找到了必要的代表使用方法
委托是对方法的引用。虽然对象可以很容易地作为参数发送到方法、构造函数或其他对象中,但是方法有点复杂。但偶尔您可能会觉得有必要将一个方法作为参数发送给另一个方法,这时您需要委托。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using MyLibrary; namespace DelegateApp { /// <summary> /// A class to define a person /// </summary> public class Person { public string Name { get; set; } public int Age { get; set; } } class Program { //Our delegate public delegate bool FilterDelegate(Person p); static void Main(string[] args) { //Create 4 Person objects Person p1 = new Person() { Name ="John", Age = 41 }; Person p2 = new Person() { Name ="Jane", Age = 69 }; Person p3 = new Person() { Name ="Jake", Age = 12 }; Person p4 = new Person() { Name ="Jessie", Age = 25 }; //Create a list of Person objects and fill it List<Person> people = new List<Person>() { p1, p2, p3, p4 }; //Invoke DisplayPeople using appropriate delegate DisplayPeople("Children:", people, IsChild); DisplayPeople("Adults:", people, IsAdult); DisplayPeople("Seniors:", people, IsSenior); Console.Read(); } /// <summary> /// A method to filter out the people you need /// </summary> /// <param name="people">A list of people</param> /// <param name="filter">A filter</param> /// <returns>A filtered list</returns> static void DisplayPeople(string title, List<Person> people, FilterDelegate filter) { Console.WriteLine(title); foreach (Person p in people) { if (filter(p)) { Console.WriteLine("{0}, {1} years old", p.Name, p.Age); } } Console.Write(" "); } //==========FILTERS=================== static bool IsChild(Person p) { return p.Age < 18; } static bool IsAdult(Person p) { return p.Age >= 18; } static bool IsSenior(Person p) { return p.Age >= 65; } } } |
我同意已经说过的每一句话,只是试着在上面加些别的词。
委托可以看作是某个/某些方法的占位符。
通过定义委托,您要对类的用户说,"请随意将与此签名匹配的任何方法分配给委托,并且每次调用我的委托时都会调用它"。
当然,典型的用法是事件。所有OnEventX委托给用户定义的方法。
委托可以为对象的用户提供一些自定义其行为的功能。大多数时候,你可以用其他方法来达到同样的目的,我不相信你会被迫创建代表。在某些情况下,这是最简单的完成任务的方法。
假设您想编写一个过程来在某个区间内集成一些实值函数f(x)[a,b]。假设我们要使用3点高斯方法来实现这一点(当然,任何方法都可以)。
理想情况下,我们需要一些看起来像:
1 2 3 4 5 6 7 8 9 | // 'f' is the integrand we want to integrate over [a, b] with 'n' subintervals. static double Gauss3(Integrand f, double a, double b, int n) { double res = 0; // compute result // ... return res; } |
所以我们可以通过任何一个
究竟应该是哪种类型?
没有代表好吧,如果没有委托,我们需要一种单一方法的接口,比如
1 2 3 4 | // Interface describing real-valued functions of one variable. interface Integrand { double eval(double x); } |
然后我们需要创建一组实现这个接口的类,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Some function class MyFunc1 : Integrand { public double eval(double x) { return /* some_result */ ; } } // Some other function class MyFunc2 : Integrand { public double eval(double x) { return /* some_result */ ; } } // etc |
然后要在我们的gauss3方法中使用它们,我们需要如下调用它:
1 2 |
Gauss3需要这样做:
1 2 3 4 | static double Gauss3(Integrand f, double a, double b, int n) { // Use the integrand passed in: f.eval(x); } |
因此,我们需要做的只是使用
1 | public delegate double Integrand(double x); |
现在,我们可以定义一些静态(或非静态)函数来遵循原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Program { public delegate double Integrand(double x); // Define implementations to above delegate // with similar input and output types static double MyFunc1(double x) { /* ... */ } static double MyFunc2(double x) { /* ... */ } // ... etc ... public static double Gauss3(Integrand f, ...) { // Now just call the function naturally, no f.eval() stuff. double a = f(x); // ... } // Let's use it static void Main() { // Just pass the function in naturally (well, its reference). double res = Gauss3(MyFunc1, a, b, n); double res = Gauss3(MyFunc2, a, b, n); } } |
对于一个简单的任务,没有接口,没有笨重的评估工具,没有对象实例化,只是简单的函数指针式用法。
当然,委托不仅仅是引擎盖下的函数指针,而是一个单独的问题(函数链接和事件)。
当想要声明您想要传递的代码块时,委托是非常有用的。例如,当使用通用重试机制时。
伪:
1 2 3 4 5 6 | function Retry(Delegate func, int numberOfTimes) try { func.Invoke(); } catch { if(numberOfTimes blabla) func.Invoke(); etc. etc. } |
或者,当您想对代码块进行后期评估时,例如,您有一些
当然,在创建事件处理程序时。您不想现在对代码进行评估,但只想在需要时进行评估,因此您注册了一个委托,可以在事件发生时调用它。
我只是想了解一下这些,所以我将分享一个例子,因为您已经有了描述,但目前我看到的一个优势是避开了循环引用样式警告,其中不能有两个项目相互引用。
让我们假设应用程序下载XML,然后将XML保存到数据库中。
我在这里有两个项目来构建我的解决方案:ftp和savedatabase。
因此,我们的应用程序首先查找任何下载和下载文件,然后调用savedatabase项目。
现在,当文件保存到数据库时,我们的应用程序需要通过上载包含元数据的文件来通知FTP站点(忽略原因,这是来自FTP站点所有者的请求)。问题是什么时候,如何解决?我们需要一个名为notifyftpcomplete()的新方法,但在我们的哪些项目中也应该保存它-ftp还是savedatabase?从逻辑上讲,代码应该存在于我们的ftp项目中。但是,这意味着必须触发notifyftpcomplete,或者必须等到保存完成,然后查询数据库以确保它在其中。我们需要做的是告诉我们的savedatabase项目直接调用notifyftpcomplete()方法,但是我们不能;我们会得到一个ciruclar引用,notifyftpcomplete()是一个私有方法。真可惜,这样会奏效的。嗯,可以。
在应用程序的代码中,我们会在方法之间传递参数,但是如果其中一个参数是notifyftpcomplete方法呢?是的,我们通过了这个方法,里面也包含了所有的代码。这意味着我们可以从任何项目的任何点执行该方法。好吧,这就是代表。这意味着,我们可以将notifyftpcomplete()方法作为参数传递给savedatabase()类。在保存时,它只执行委托。
看看这个粗糙的例子是否有帮助(伪代码)。我们还假设应用程序从ftp类的begin()方法开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class FTP { public void Begin() { string filePath = DownloadFileFromFtpAndReturnPathName(); SaveDatabase sd = new SaveDatabase(); sd.Begin(filePath, NotifyFtpComplete()); } private void NotifyFtpComplete() { //Code to send file to FTP site } } class SaveDatabase { private void Begin(string filePath, delegateType NotifyJobComplete()) { SaveToTheDatabase(filePath); //InvokeTheDelegate - here we can execute the NotifyJobComplete method at our preferred moment in the application, despite the method being private and belonging to a different class. NotifyJobComplete.Invoke(); } } |
因此,通过解释,我们现在可以用这个控制台应用程序使用C来实现它。#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | using System; namespace ConsoleApplication1 { //I've made this class private to demonstrate that the SaveToDatabase cannot have any knowledge of this Program class. class Program { static void Main(string[] args) { //Note, this NotifyDelegate type is defined in the SaveToDatabase project NotifyDelegate nofityDelegate = new NotifyDelegate(NotifyIfComplete); SaveToDatabase sd = new SaveToDatabase(); sd.Start(nofityDelegate); Console.ReadKey(); } //this is the method which will be delegated - the only thing it has in common with the NofityDelegate is that it takes 0 parameters and that it returns void. However, it is these 2 which are essential. It is really important to notice that it writes a variable which, due to no constructor, has not yet been called (so _notice is not initialized yet). private static void NotifyIfComplete() { Console.WriteLine(_notice); } private static string _notice ="Notified"; } public class SaveToDatabase { public void Start(NotifyDelegate nd) { Console.WriteLine("Yes, I shouldn't write to the console from here, it's just to demonstrate the code executed."); Console.WriteLine("SaveToDatabase Complete"); Console.WriteLine(""); nd.Invoke(); } } public delegate void NotifyDelegate(); } |
我建议您逐步完成代码,看看什么时候调用通知,什么时候调用方法(委托),我希望这样可以让事情变得非常清楚。
但是,最后,通过更改委托类型以包含参数,我们可以使它更有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | using System.Text; namespace ConsoleApplication1 { //I've made this class private to demonstrate that the SaveToDatabase cannot have any knowledge of this Program class. class Program { static void Main(string[] args) { SaveToDatabase sd = new SaveToDatabase(); //Please note, that although NotifyIfComplete() takes a string parameter, we do not declare it - all we want to do is tell C# where the method is so it can be referenced later - we will pass the paramater later. NotifyDelegateWithMessage notifyDelegateWithMessage = new NotifyDelegateWithMessage(NotifyIfComplete); sd.Start(notifyDelegateWithMessage ); Console.ReadKey(); } private static void NotifyIfComplete(string message) { Console.WriteLine(message); } } public class SaveToDatabase { public void Start(NotifyDelegateWithMessage nd) { //To simulate a saving fail or success, I'm just going to check the current time (well, the seconds) and store the value as variable. string message = string.Empty; if (DateTime.Now.Second > 30) message ="Saved"; else message ="Failed"; //It is at this point we pass the parameter to our method. nd.Invoke(message); } } public delegate void NotifyDelegateWithMessage(string message); } |
Delegates Overview
Delegates have the following properties:
- Delegates are similar to C++ function pointers, but are type safe.
- Delegates allow methods to be passed as parameters.
- Delegates can be used to define callback methods.
- Delegates can be chained together; for example, multiple methods can be called on a single event.
- Methods don't need to match the delegate signature exactly. For more information, see Covariance and Contra variance.
- C# version 2.0 introduces the concept of Anonymous Methods, which permit code blocks to be passed as parameters in place of a separately defined method.
我认为委托是匿名接口。在许多情况下,只要您需要一个具有单个方法的接口,就可以使用它们,但是您不希望定义该接口的开销。
委托是一个简单的类,用于指向具有特定签名的方法,本质上成为一个类型安全的函数指针。委托的目的是在一个方法(或方法)完成后,以结构化的方式促进对另一个方法(或方法)的调用。
虽然可以创建一组广泛的代码来执行此功能,但您也不需要。您可以使用代理。
创建委托很容易。使用"delegate"关键字将类标识为委托。然后指定类型的签名。