当,为什么使用委托?

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

中使用它们

我也想知道当我不得不使用委托时,我没有其他选择

谢谢你的帮助!

编辑:我想我已经找到了一个委托的必要使用 在这里

598436 次浏览

我认为委托是匿名的接口。在许多情况下,只要需要带有单个方法的接口,就可以使用它们,但不希望定义该接口的开销。

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

伪:

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

或者当你想要对代码块进行后期求值时,比如一个函数中有一些Transform动作,并且想要有一个BeforeTransform和一个AfterTransform动作,可以在Transform函数中求值,而不必知道BeginTransform是否被填充,或者它必须转换什么。

当然,在创建事件处理程序时也是如此。您不希望现在计算代码,而只希望在需要时计算代码,因此您可以注册一个可以在事件发生时调用的委托。

我同意已经说过的一切,只是试着用另一种说法来解释。

委托可以被视为一个或多个方法的占位符。

通过定义委托,你是在对类的用户说,&;__abc0 &;

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

委托可以为对象的用户提供一些自定义行为的能力。 大多数时候,你可以使用其他方法来达到同样的目的,我不相信你可以通过强迫来创建委托。在某些情况下,这是完成一件事最简单的方法

代表概述

委托具有以下属性:

  • 委托类似于c++的函数指针,但是是类型安全的。
  • 委托允许将方法作为参数传递。
  • 委托可用于定义回调方法。
  • 代表可以被链接在一起;例如,可以对单个事件调用多个方法。
  • 方法不需要完全匹配委托签名。更多信息请参见协方差和反方差。
  • c# 2.0版引入了匿名方法的概念,它允许代码块作为参数传递,以代替单独定义的方法。

委托是一个简单的类,用于指向具有特定签名的方法,本质上成为类型安全的函数指针。委托的目的是在一个方法完成后,以结构化的方式方便对另一个(或多个)方法的回调。

虽然可以创建一组大量的代码来执行此功能,但您不需要这样做。你可以使用委托。

创建委托很容易做到。使用"delegate"关键字将类标识为委托。然后指定类型的签名。

假设你想写一个过程,在某个区间[a, b]上对某个实值函数f (x)积分。假设我们想要使用3点高斯方法来做到这一点(当然,任何方法都可以)。

理想情况下,我们想要这样的函数:

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

所以我们可以传入任意Integrandf,并得到它在闭合区间上的定积分。

Integrand应该是什么类型?

没有代表

好吧,如果没有委托,我们需要某种带有单个方法的接口,比如eval声明如下:

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

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

// 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中的任意函数。

与代表

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,没有对象实例化,对于一个简单的任务,只有类似函数指针的简单用法。

当然,委托不仅仅是底层的函数指针,但这是一个单独的问题(函数链和事件)。

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


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("\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;
}
}
}

输出:

Children:
Jake, 12 years old




Adults:
John, 41 years old
Jane, 69 years old
Jessie, 25 years old




Seniors:
Jane, 69 years old

我只是在思考这些,所以我将分享一个例子,因为你已经有了描述,但目前我看到的一个优势是绕过循环引用风格的警告,你不能让两个项目相互引用。

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

我在这里有两个项目来构建我的解决方案:FTP和SaveDatabase。

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

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

在我们的应用程序代码中,我们将在方法之间传递参数,但是如果这些参数之一是NotifyFtpComplete方法呢?是的,我们传递了方法,里面还有所有的代码。这意味着我们可以在任何项目的任何位置执行该方法。这就是委托。这意味着,我们可以将NotifyFtpComplete()方法作为参数传递给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)
{
/* I shouldn't write to the console from here,
* just for demonstration purposes
*/
Console.WriteLine("SaveToDatabase Complete");
Console.WriteLine(" ");
nd.Invoke();
}
}
public delegate void NotifyDelegate();
}

我建议您逐步执行代码,并查看何时调用_notice以及何时调用方法(委托),我希望这样可以使事情非常清楚。

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

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 parameter later.
*/
var 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);
}