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

我也想知道什么时候我必须使用委托,我没有其他的选择。

谢谢你的帮助!

编辑:我认为我在这里找到了委托的必要用途


当前回答

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

其他回答

假设你想写一个过程,在某个区间[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;
}

我们可以传入任意被积函数f,得到它在闭区间上的定积分。

被积函数应该是什么类型?

没有代表

好吧,如果没有委托,我们需要某种只有一个方法的接口,比如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项目。

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.

在我们的应用程序代码中,我们将在方法之间传递参数,但是如果这些参数之一是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);
}

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

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

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

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

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

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