何时以及为什么要使用代表?

我是相对较新的C#,我想知道什么时候适当地使用委托 。 它们在事件声明中被广泛使用,但是什么时候应该在我自己的代码中使用它们, 为什么它们有用呢? 为什么不使用别的东西呢?

我也想知道什么时候我必须使用代表,我没有别的select

感谢您的帮助!

编辑:我想我已经在这里find代表必要用途

我同意所有已经说过的话,只是想把其他的一些话放在上面。

代表可以被看作一个/某些方法的占位符。

通过定义一个委托,你正在对你的类的用户说:“ 请随意将任何匹配这个签名的方法分配给这个委托,并且每次我的委托被调用时它都会被调用 ”。

典型的用途当然是事件。 所有的OnEventX 委托给用户定义的方法。

代表可以为用户提供一些自定义行为的function。 大多数情况下,您可以使用其他方式来达到相同的目的,我不认为您可能会被迫创build代表。 这只是在某些情况下完成任务的最简单的方法。

委托是对方法的引用。 而对象可以很容易地作为参数发送到方法,构造函数或其他方法,方法则有点棘手。 但是每隔一段时间,您可能会觉得需要将方法作为参数发送到另一个方法,这就是您需要委托的时候。

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 }; 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("\n\n"); } //==========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; } } } 

假设你想写一个过程来在某个区间[a,b]上集成一个实值函数fx )。 假设我们想用三点高斯方法来做到这一点(当然,任何事情都可以)。

理想情况下,我们需要一些如下所示的函数:

 // '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; } 

所以我们可以传递任意的积分,在闭区间内得到它的定积分。

Integrand应该是什么types?

没有代表

那么,没有委托,我们需要一种接口与一个单一的方法,说eval声明如下:

 // Interface describing real-valued functions of one variable. interface Integrand { double eval(double x); } 

然后我们需要创build一大堆实现这个接口的类,如下所示:

 // 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方法中使用它们,我们需要如下调用它:

 double res1 = Gauss3(new MyFunc1(), -1, 1, 16); double res2 = Gauss3(new MyFunc2(), 0, Math.PI, 16); 

Gauss3需要做如下的样子:

 static double Gauss3(Integrand f, double a, double b, int n) { // Use the integrand passed in: f.eval(x); } 

所以我们只需要在Guass3使用我们的任意函数Guass3

与代表

 public delegate double Integrand(double x); 

现在我们可以定义一些遵循该原型的静态(或不)的函数:

 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); } } 

没有接口,没有笨重的.eval的东西,没有对象的实例,只是简单的函数指针像使用,为一个简单的任务。

当然,代表不仅仅是函数指针,而是一个单独的问题(函数链和事件)。

当想要声明要传递的代码块时,委托非常有用。 例如,当使用通用重试机制。

伪:

 function Retry(Delegate func, int numberOfTimes) try { func.Invoke(); } catch { if(numberOfTimes blabla) func.Invoke(); etc. etc. } 

或者当你想对代码块进行晚期评估的时候,比如你有一些Transform动作的函数,并且希望在你的Transform函数中可以计算一个BeforeTransform和一个AfterTransform动作,而不必知道BeginTransform是否被填充,或者它有什么变化。

当然,创build事件处理程序。 您现在不想评估代码,但只有在需要时,才能注册可在事件发生时调用的代理。

代表概览

代表具有以下属性:

  • 委托类似于C ++函数指针,但types安全。
  • 代表允许方法作为parameter passing。
  • 代表可以用来定义callback方法。
  • 代表可以链接在一起; 例如,可以在单个事件上调用多个方法。
  • 方法不需要完全匹配委托签名。 有关更多信息,请参阅协方差和对比方差。
  • C#版本2.0引入了匿名方法的概念,允许代码块作为parameter passing,而不是单独定义的方法。

我刚刚开始讨论这些问题,所以我将分享一个示例,因为您已经有了描述,但是目前我看到的一个优点是避开了循环引用样式警告,您不能让2个项目引用每个项目其他。

我们假设应用程序下载一个XML,然后将XML保存到数据库。

我有2个项目在这里构build我的解决scheme:FTP和SaveDatabase。

所以,我们的应用程序首先查找任何下载并下载文件,然后调用SaveDatabase项目。

现在,我们的应用程序需要通过上传带有元数据的文件(忽略为什么,这是来自FTP站点所有者的请求)来将文件保存到数据库时通知FTP站点。 问题是在什么时候,怎么样? 我们需要一个名为NotifyFtpComplete()的新方法,但是我们的哪个项目也应该保存–FTP或SaveDatabase? 逻辑上,代码应该存在于我们的FTP项目中。 但是,这意味着我们的NotifyFtpComplete将不得不被触发,或者它将不得不等待保存完成,然后查询数据库以确保它在那里。 我们需要做的是告诉我们的SaveDatabase项目直接调用NotifyFtpComplete()方法,但是我们不能; 我们会得到一个ciruclar引用,NotifyFtpComplete()是一个私有方法。 真可惜,这会有用的。 那么可以。

在我们应用程序的代码中,我们会在方法之间传递参数,但是如果其中一个参数是NotifyFtpComplete方法呢。 是的,我们通过这个方法,所有的代码也在里面。 这意味着我们可以从任何项目的任何时候执行该方法。 那么这就是代表呢。 这意味着,我们可以将NotifyFtpComplete()方法作为parameter passing给我们的SaveDatabase()类。 在它节省的地方,它只是执行委托。

看看这个粗糙的例子是否有帮助(伪代码)。 我们还会假定应用程序以FTP类的Begin()方法开始。

 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#

 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(); } 

我build议你单步执行代码,看看_notice是什么时候被调用的,当调用方法(delegate)的时候,我希望能够把事情弄清楚。

然而,最后,我们可以通过改变委托types来包含一个参数来使它更有用。

 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 nofityDelegateWithMessage = new NotifyDelegateWithMessage(NotifyIfComplete); sd.Start(nofityDelegateWithMessage); 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); } 

我认为代表是匿名接口 。 在许多情况下,只要需要使用单一方法的接口,就可以使用它们,但不需要定义该接口的开销。

委托是一个简单的类,用于指向具有特定签名的方法,实质上成为types安全的函数指针。 代表的目的是在结构化的方式之后促进callback到另一个方法(或多个方法)。

尽pipe可以创build大量代码来执行此function,但您并不需要。 您可以使用委托。

创build委托很容易。 使用“委托”关键字将该类标识为委托。 然后指定types的签名。