当&为什么要使用代表?

时间:2010-01-07 09:51:56

标签: c# .net delegates

我在C#中比较新,&我想知道何时适当地使用代表。 它们被广泛用于事件声明中,但何时应该在我自己的代码中使用它们为什么它们有用? 为什么不使用其他东西?

我也想知道当我必须使用代表时我没有其他选择

感谢您的帮助!

编辑:我认为我找到了必要的代表使用 here

8 个答案:

答案 0 :(得分:272)

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

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

答案 1 :(得分:257)

我同意已经说过的所有内容,只是试图在其上加上一些其他的话。

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

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

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

代表可以向对象的用户提供一些自定义行为的功能。 大多数情况下,您可以使用其他方式来实现相同的目的,我不相信您可以强制来创建委托。在某些情况下,这是完成任务的最简单方法。

答案 2 :(得分:142)

假设您要编写一个过程来在某个区间[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;
}

因此,我们可以传入任何Integrand f ,并在闭区间内获得其明确的积分。

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东西,没有对象实例化,只是简单的函数指针,如简单的任务。

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

答案 3 :(得分:27)

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

伪:

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

或者,如果您想对代码块进行后期评估,例如您有一些Transform操作的功能,并希望执行BeforeTransformAfterTransform操作在您的转换函数中进行评估,而不必知道BeginTransform是否已填充,或者它必须转换的内容。

当然,在创建事件处理程序时。您现在不想评估代码,但仅在需要时才进行评估,因此您可以注册可在事件发生时调用的委托。

答案 4 :(得分:21)

  

代表概述

     

代表具有以下属性:

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

答案 5 :(得分:21)

我只是围绕着这些,所以我会分享一个例子,因为你已经有了描述但是目前我看到的一个优点是绕过循环参考样式警告,你不能有2相互引用的项目。

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

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

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

现在,我们的应用程序需要通过上传带有Meta数据的文件将文件保存到数据库时通知FTP站点(忽略原因,这是来自FTP站点所有者的请求)。问题在于什么时候以及如何?我们需要一个名为NotifyFtpComplete()的新方法,但是我们应该保存哪些项目--FTP或SaveDatabase?从逻辑上讲,代码应该存在于我们的FTP项目中。但是,这意味着必须触发我们的NotifyFtpComplete,或者它必须等到保存完成,然后查询数据库以确保它在那里。我们需要做的是告诉我们的SaveDatabase项目直接调用NotifyFtpComplete()方法,但我们不能;我们得到一个ciruclar引用,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)
        {
            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();
}

我建议你逐步完成代码,看看_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 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);
}

答案 6 :(得分:9)

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

答案 7 :(得分:3)

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

虽然可以创建一组广泛的代码来执行此功能,但您也不需要。您可以使用代理人。

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