C# 何时以及为何使用委托?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2019402/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 22:43:22  来源:igfitidea点击:

When & why to use delegates?

c#.netdelegates

提问by iChaib

I'm relatively new in C#, & I'm wondering when to use Delegates appropriately. they are widely used in events declaration, but when should I use them in my own code and why are they useful?why not to use something else?

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

I'm also wondering when I have to use delegates and I have no other alternative.

我也想知道什么时候必须使用委托,而我别无选择

Thank you for the help!

感谢您的帮助!

EDIT: I think I've found a necessary use of Delegateshere

编辑:我想我在这里找到了对委托必要用途

采纳答案by Benoit Vidis

I agree with everything that is said already, just trying to put some other words on it.

我同意已经说过的一切,只是想在上面加上一些其他的话。

A delegate can be seen as a placeholder for a/some method(s).

委托可以被视为一个/某些方法的占位符。

By defining a delegate, you are saying to the user of your class, "Please feel free to assign, any method that matches this signature, to the delegate and it will be called each time my delegate is called".

通过定义委托,您对类的用户说,“请随意将与此签名匹配的任何方法分配给委托,并且每次调用我的委托时都会调用它”。

Typical use is of course events. All the OnEventX delegateto the methods the user defines.

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

Delegates are useful to offer to the userof your objects some ability to customize their behavior. Most of the time, you can use other ways to achieve the same purpose and I do not believe you can ever be forcedto create delegates. It is just the easiest way in some situations to get the thing done.

委托对于向对象的用户提供一些自定义其行为的能力很有用。大多数情况下,您可以使用其他方式来实现相同的目的,我认为您永远不会被迫创建委托。在某些情况下,这只是完成任务的最简单方法。

回答by Mark Seemann

I consider delegates to be Anonymous Interfaces. In many cases you can use them whenever you need an interface with a single method, but you don't want the overhead of defining that interface.

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

回答by Jan Jongboom

Delegates are extremely useful when wanting to declare a block of code that you want to pass around. For example when using a generic retry mechanism.

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

Pseudo:

伪:

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

Or when you want to do late evaluation of code blocks, like a function where you have some Transformaction, and want to have a BeforeTransformand an AfterTransformaction that you can evaluate within your Transform function, without having to know whether the BeginTransformis filled, or what it has to transform.

或者当您想要对代码块进行后期评估时,例如您有一些Transform操作的函数,并且想要拥有一个可以在 Transform 函数中评估BeforeTransformAfterTransform操作,而无需知道 是否BeginTransform已填充,或者它是什么必须转型。

And of course when creating event handlers. You don't want to evaluate the code now, but only when needed, so you register a delegate that can be invoked when the event occurs.

当然,在创建事件处理程序时。您现在不想评估代码,而只是在需要时评估,因此您注册了一个可以在事件发生时调用的委托。

回答by Lukas ?alkauskas

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.

代表概览

委托具有以下属性:

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

回答by Pankaj

A delegate is a simple class that is used to point to methods with a specific signature, becoming essentially a type-safe function pointer. A delegate's purpose is to facilitate a call back to another method (or methods), after one has been completed, in a structured way.

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

While it could be possible to create an extensive set of code to perform this functionality, you don't need too. You can use a delegate.

虽然可以创建大量代码来执行此功能,但您不需要太多。您可以使用委托。

Creating a delegate is easy to do. Identify the class as a delegate with the "delegate" keyword. Then specify the signature of the type.

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

回答by Alex Budovski

Say you want to write a procedure to integrate some real-valued function f(x) over some interval [a, b]. Say we want to use the 3-Point Gaussian method to do this (any will do, of course).

假设您想编写一个过程来对某个区间 [a, b] 上的某个实值函数f( x)进行积分。假设我们想使用 3-Point Gaussian 方法来执行此操作(当然,任何方法都可以)。

Ideally we want some function that looks like:

理想情况下,我们想要一些看起来像这样的函数:

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

So we can pass in any Integrand, f, and get its definite integral over the closed interval.

所以我们可以传入 any Integrand, f,得到它在闭区间上的定积分。

Just what type should Integrandbe?

应该Integrand是什么类型?

Without Delegates

没有代表

Well, without delegates, we'd need some sort of interface with a single method, say evaldeclared as follows:

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

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

Then we'd need to create a whole bunch of classes implementing this interface, as follows:

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

// 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

Then to use them in our Gauss3 method, we need to invoke it as follows:

然后要在我们的 Gauss3 方法中使用它们,我们需要按如下方式调用它:

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

And Gauss3 needs to do the look like the following:

Gauss3 需要做如下所示:

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

So we need to do all that just to use our arbitrary functions in Guass3.

所以我们需要做所有这些只是为了在Guass3.

With Delegates

与代表

public delegate double Integrand(double x);

Now we can define some static (or not) functions adhering to that prototype:

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

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

No interfaces, no clunky .eval stuff, no object instantiation, just simple function-pointer like usage, for a simple task.

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

Of course, delegates are more than just function pointers under the hood, but that's a separate issue (function chaining and events).

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

回答by dhaval8087

A delegate is a reference to a method. Whereas objects can easily be sent as parameters into methods, constructor or whatever, methods are a bit more tricky. But every once in a while you might feel the need to send a method as a parameter to another method, and that's when you'll need delegates.

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

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

Output:

输出:

Children:
Jake, 12 years old


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


Seniors:
Jane, 69 years old

回答by Dave

I've just go my head around these, and so I'll share an example as you already have descriptions but at the moment one advantage I see is to get around the Circular Reference style warnings where you can't have 2 projects referencing each other.

我只是围绕这些,所以我会分享一个例子,因为你已经有了描述,但目前我看到的一个优势是绕过循环引用样式警告,在那里你不能有 2 个项目引用每个其他。

Let's assume an application downloads an XML, and then saves the XML to a database.

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

I have 2 projects here which build my solution: FTP and a SaveDatabase.

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

So, our application starts by looking for any downloads and downloading the file(s) then it calls the SaveDatabase project.

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

Now, our application needs to notify the FTP site when a file is saved to the database by uploading a file with Meta data (ignore why, it's a request from the owner of the FTP site). The issue is at what point and how? We need a new method called NotifyFtpComplete() but in which of our projects should it be saved too - FTP or SaveDatabase? Logically, the code should live in our FTP project. But, this would mean our NotifyFtpComplete will have to be triggered or, it will have to wait until the save is complete, and then query the database to ensure it is in there. What we need to do is tell our SaveDatabase project to call the NotifyFtpComplete() method direct but we can't; we'd get a ciruclar reference and the NotifyFtpComplete() is a private method. What a shame, this would have worked. Well, it can.

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

During our application's code, we would have passed parameters between methods, but what if one of those parameters was the NotifyFtpComplete method. Yup, we pass the method, with all of the code inside as well. This would mean we could execute the method at any point, from any project. Well, this is what the delegate is. This means, we can pass the NotifyFtpComplete() method as a parameter to our SaveDatabase() class. At the point it saves, it simply executes the delegate.

在我们的应用程序代码中,我们会在方法之间传递参数,但是如果这些参数之一是 NotifyFtpComplete 方法呢?是的,我们传递了方法,其中也包含了所有代码。这意味着我们可以在任何项目的任何时候执行该方法。嗯,这就是代表。这意味着,我们可以将 NotifyFtpComplete() 方法作为参数传递给我们的 SaveDatabase() 类。在它保存时,它只是执行委托。

See if this crude example helps (pseudo code). We will also assume that the application starts with the Begin() method of the FTP class.

看看这个粗略的例子是否有帮助(伪代码)。我们还将假设应用程序以 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();
    }
}

So, with that explained, we can do it for real now with this Console Application using C#

因此,通过解释,我们现在可以使用 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();
}

I suggest you step through the code and see when _notice is called and when the method (delegate) is called as this, I hope, will make things very clear.

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

However, lastly, we can make it more useful by changing the delegate type to include a parameter.

但是,最后,我们可以通过更改委托类型以包含参数来使其更有用。

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