C#中的代表

时间:2009-11-14 18:55:53

标签: c# delegates

我在理解C#中的委托如何工作方面遇到了一些麻烦。我有很多代码示例,但我仍然无法正确掌握它。

有人可以用“普通英语”向我解释一下吗?当然!代码示例会有所帮助,但我认为我需要更多地描述它是如何/为什么有效。

编辑:

嗯,问题是:为什么代表们工作?什么是整个过程的“流程图”?

使用委托的先决条件是什么?

我希望这会使问题更清楚。

10 个答案:

答案 0 :(得分:24)

考虑委托的一种方法就像对函数的引用。例如,假设您在窗口中有一个按钮,并且您希望在单击按钮时发生某些事情。您可以将代理附加到按钮的Click事件,每当用户单击此按钮时,您的函数将被执行。

class MyWindow : Window
{
    Button _button;

    public MyWindow()
    {
        _button = new Button();
        // place the button in the window
        _button.Click += MyWindow.ButtonClicked;
    }

    static void ButtonClicked(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Button Clicked");
    }
}

注意我如何使ButtonClicked成为一个静态函数 - 我想对接下来的非静态函数提出一个观点。假设ButtonClicked是一个非静态成员:

class MyWindow : Window
{
    Button _button;
    int _numClicked = 0;

    public MyWindow()
    {
        this._button = new Button();
        // place the button in the window
        this._button.Click += this.ButtonClicked;
    }

    void ButtonClicked(object sender, RoutedEventArgs e)
    {
        this._numClicked += 1;
        MessageBox.Show("Button Clicked " + this._numClicked + " times");
    }
}

现在委托包含对函数“ButtonClicked”的引用和实例“this”,该方法被调用。 MyWindow构造函数中的实例“this”和ButtonClicked中的“this”是相同的。

这是一个称为 closures 的概念的特定情况,它允许在创建委托时“保存”状态 - 当前对象,局部变量等。在上面的例子中,我们使用了委托中的构造函数中的“this”。我们可以做的不仅仅是:

class MyWindow : Window
{
    Button _button;
    int _numClicked = 0;

    public MyWindow(string localStringParam)
    {
        string localStringVar = "a local variable";
        this._button = new Button();
        // place the button in the window
        this._button.Click += new RoutedEventHandler(
            delegate(object sender, RoutedEventArgs args)
            {
                this._numClicked += 1;
                MessageBox.Show("Param was: " + localStringParam + 
                     " and local var " + localStringVar +
                     " button clicked " + this._numClicked + " times");
            });
    }
}

这里我们创建了一个匿名委托 - 一个没有明确名称的函数。引用此函数的唯一方法是使用RoutedEventHandler委托对象。此外,此函数存在于MyWindow构造函数的范围内,因此它可以访问所有本地参数,变量和成员实例“this”。即使在MyWindow构造函数退出后,它仍将继续保持对局部变量和参数的引用。

作为旁注,委托也将持有对象实例的引用 - “this” - 即使在删除了对类a的所有其他引用之后。因此,为了确保对类进行垃圾回收,应删除非静态成员方法(或在一个范围内创建的委托)的所有委托。

答案 1 :(得分:6)

嗯,委托是一种类型。委托类型的变量可以引用或指向函数。

这为您提供了一种调用方法的间接方法,因此可以在运行时选择方法。因此,您可以拥有包含方法的变量,参数和属性。这些属性称为事件。

再完成一个代码示例:

   delegate void ADelegate();  // the delegate type

   void Foo() { ... }   // a compatible method
   void Bar() { ... }   // a compatible method

   void Main()
   {
      ADelegate funcPtr;  // a delegate variable

      if (aCondition)
        funcPtr = Foo;  // note: _not_ Foo(), Foo is not executed here
      else
        funcPtr = Bar;

      funcPtr(); // calls Foo or Bar depending on aCondition
   }

使用委托变量并不常见。但是您可以使用委托参数例如Sort方法来选择升序或降序排序。

  delegate int Compare(MyClass a, MyClass b);  // the delegate type

  void int CompareUp(MyClass a, MyClass b) { ... }   
  void int CompareDn(MyClass a, MyClass b) { ... }   

  void Sort(MyClass[] data, Compare comparer) { ... }

你可能知道基于代表的(特殊类型)属性事件。

答案 2 :(得分:6)

委托是指向某处定义的方法的函数指针。

假设您有 BankAccount 课程,并且只要他/她的余额低于100美元,您就必须向客户发送电子邮件。然后,自然趋势是在Balance属性设置器中添加一个检查,以查看客户的余额是否低于100美元,如果是,则触发电子邮件。但这种设计并不灵活。

上述方法的缺点:

将来,肯定会要求向客户发送短信而不是电子邮件。很少有客户选择电子邮件和短信。因此,无论何时您需要通知客户任何更改,您都将修改 BankAccount 类。这违反了 OPEN FOR EXTENSION AND CLOSED FOR MODIFICATION 坚实的设计原则。

使用DELEGATES的替代解决方案:

  1. 定义 NotifyCustomer()方法,该方法处理在 BankAccount 类之外向客户发送有关低余额的通知。

    < / LI>
  2. 修改 BankAccount 类以定义委托并在构造函数中接受它的值。

  3. 创建 BankAccount 类时,请传入步骤1中创建的 NotifyCustomer()方法。

  4. 在BankAccount的类Balance setter中,检查余额是否低于100美元。如果是,请调用该委托。

  5. 调用 BankAccount 类之外定义的 NotifyCustomer()方法,从而发送定义的通知。

  6. 将来,如果有新的方式通知客户,则 BankAccount 类不需要进行任何更改。

    使用代表设计的优点:

    1. 放松联接 BankAccount 类不知道通知客户的硬编码逻辑。

    2. 遵循OPEN FOR EXTENSION和CLOSED FOR MODIFICATION原则:每当通知客户的媒介发生变化时,您无需更改 BankAccount 类。所以现在你会自豪地说你的 BankAccount 课程设计遵循设计原则。

    3. 如果您想通过示例了解有关委托的更多信息,请阅读What are delegates and why we need them.

答案 3 :(得分:2)

它的反转原理。通常,您编写调用方法的代码,并且在编写代码时已知您调用的方法。代理允许您匿名调用方法。那就是你不知道程序运行时调用的实际方法。

它可用于分离应用程序不同部分的关注点。因此,您可以拥有一些在数据存储上执行任务的代码。您可能有其他代码来处理数据。数据上的过程不需要知道数据存储的结构,数据存储不应该依赖于数据的使用。

可以假设某些与数据存储结构无关的数据,编写处理代码。这样,我们可以更改数据存储的结构,而不用担心影响数据上的进程。

答案 4 :(得分:2)

您可以将委托视为将代码视为数据的一种方式。如果您创建委托,则它是一种类型。这种类型的变量可能指向特定的方法(符合委托定义)。

这意味着您可以将一段代码视为数据,例如将其传递给方法。由于委托指向代码(或null),您也可以通过变量调用它指向的代码。

这允许一些非常有用的模式。典型的例子是如何对集合进行排序。通过允许调用者提供实现对特定元素进行排序的代理的委托,排序方法不必知道任何有关此内容的信息。

同样的想法广泛用于LINQ的许多方法。即您传入一个处理某个特定任务的委托(或更常见的是lambda),并且有问题的LINQ方法将调用它来完成任务。

答案 5 :(得分:2)

委托是一种引用类型,它通过委托实例调用单个/多个方法。它包含方法的引用.Delegates可用于在单个事件上处理(调用/调用)多个方法。委托可用于定义异步方法。 这是代表的一个例子 首先,我们创建一个类。我们在其中声明了委托。我们在类中创建了一个调用委托的方法。

public class simpleinterest
{
    public delegate void intcal(double i);  //declare delegate
    public event intcal interest; //create delegate object
    public void calculate(double p, double n,double r)
    {
       interest(p*n*r/100);   //invoke delegate
    }

}

在我们的程序中,我们进行映射。即我们指定在调用委托时将触发哪个事件。

    private void btn_Click(object sender, RoutedEventArgs e)
    {
        simpleinterest s1 = new simpleinterest();
        s1.interest+=new simpleinterest.intcal(s1_interest);//mapping

        s1.calculate(1000,3,10);

    }
    void s1_interest(double r)
    {
         MessageBox.Show("Amount:" + r.ToString());

    }

答案 6 :(得分:2)

1)首先,您必须了解为什么/何时需要代表,它解决的问题是什么。

根据我的经验,我主要使用它们允许用户自定义对象的行为

Immagine Grid 组件,允许开发人员自定义每列的呈现方式。 例如,当数字小于零时,你想要写一个红色值。

创建Grid的开发人员不知道用户如何自定义输出,因此需要一种机制让组件的用户将一些逻辑注入组件

2)然后你必须了解委托的工作方式

令人困惑的是你必须编写的奇怪的代码,以及多种方式你必须做同样的事情。

这是网格类:

// the grid let the programmer that will use it to customize the output
public class Grid{

    // 1) First I declare only the interface of the delegate
    public delegate String ValueFormatterDelegate(String v);

    // 2) I declare a handler of the implementation of the delegate
    public ValueFormatterDelegate ValueFormatterHandler; 

    // 3) I call the handler inside the Print method
    public void Print(String x){
        Console.WriteLine( ValueFormatterHandler.Invoke(x) );
    }

}
  

// 1)首先,我只声明委托的接口   public delegate String ValueFormatterDelegate(String v);

请注意,这与普通方法相似,但是:

  • 它有一个委托关键字
  • 它没有实施

通过这种方式我说:“将输出格式化的方法有这个接口:它将输入一个字符串作为输入,它将输出一个字符串”

它记得我接口方法的定义。

  

// 2)我声明了委托实现的处理程序   public ValueFormatterDelegate ValueFormatterHandler;

现在我必须创建一个委托类型的属性来处理这个方法的实现。

  

// 3)我在Print方法中调用处理程序   public void Print(String x){     Console.WriteLine(ValueFormatterHandler.Invoke(x));   }

在Print方法中,我可以使用将链接真实实现的处理程序。

ValueFormatterHandler的类型为ValueFormatterDelegate 和ValueFormatterDelegate是广告代理 和.Invoke是委托类型的方法

这是一个使用我的Grid类的程序,可以动态地对其进行个性化。 这里的问题是你必须做同样事情的许多方法。

using System;

public class Program{
    public static void Main(){

        var printer = new Printer();

        // METHOD 1 : link to a named method
        // here i link the handler of the delegate to a real method
        // the FormatXXX is a static method defined at the ed of this code
        printer.ValueFormatter = FormatXXX;

        // when i call Print("hello")
        printer.Print("hello"); // XXhelloXX

        // METHOD 2 : anonimous method
        // think at this like a method but without a name
        // FormatYY (String x ){ return "YY"+x+"YY"; };
        //  become
        // delegate (String x ){ return "YY"+x+"YY"; };
        printer.ValueFormatter = delegate (String x ){ return "YY"+x+"YY"; };
        printer.Print("hello"); // YYhelloYY

        // METHOD 3 : anonimous method using lambda
        // as you can note the type of parameter x is inferred from the delegate declaration
        // public delegate String ValueFormatterDelegate(String v);
        printer.ValueFormatter = (x)=>"KK" + x + "KK";

    }

    public static String FormatXXX(String y){
        return "XX"+ y +"XX";
    }

}

答案 7 :(得分:1)

代理是引用类型,委托引用方法。这称为封装方法。创建委托时,指定方法签名和返回类型。您可以使用该委托封装任何匹配方法。您可以使用委托关键字创建委托,然后是返回类型以及可以委派给它的方法的签名,如下所示:

public delegate void HelloFunctionDelegate(string message);
class Program
{
 static void Main()
{
HelloFunctionDelegate del = new HelloFunctionDelegate(Hello);
del("Hello from Delegate");
}
public static void Hello(string strMessage)
{
 Console.WriteLine(strMessage);
}
}

来自Delegate

的输出是Hello

答案 8 :(得分:1)

在c#中延迟:它定义了它可以调用的方法的签名。换句话说,我们可以说它包装了它可以调用的方法的引用。 以下是 代表的用途:

  1. 它提供了在.NET框架中实现回调功能的机制。
  2. 它提供了顺序调用多个方法的功能。
  3. 它具有实现异步方法调用的能力。
  4. 它支持静态和实例方法。

    以下是内部工作原理的解释。

    //这是代表的声明。

    public delegate void DisplayNamme(string name);
    

    在运行时CLR为代理创建一个类,如下所示。

    public class DisplayNamme : System.MulticastDelegate{
    
       // It is a contructor
       public DisplayNamme(Object @object, IntPtr method);
    
       // It is the method with the same prototype as defined in the source code. 
       public void Invoke(String name);
    
    // This method allowing the callback to be asynchronouslly.
    
     public virtual IAsyncResult BeginInvoke(String name, 
     AsyncCallback callback, Object @object); 
    
     public virtual void EndInvoke(IAsyncResult result); 
    
    }
    

    我们可以通过ILDasm.exe 工具查看。 使用此工具破解DLL。

    构造函数有两个参数:IntPrt引用传递给函数的方法的名称,@ object引用隐式传递给构造函数的对象的引用。

    CLR使用委托的Invoke方法来调用回调方法。

    下面是使用委托的回调方法的实现。

    // Declare Delegates
        public delegate void DisplayNamme(string name);
        class Program
        {
           public static void getName(string name)
            {
                Console.WriteLine(name);
            }
           public static void ShowName(DisplayNamme dn, string name)
            {
            // callback method calling. We can call it in two ways. 
               dn(name);
              // or explicitly
                dn.Invoke(name);
        }
          static void Main(string[] args)
            {
                DisplayNamme delDN = getName;
                Program.ShowName(delDN, "CallBack");
                Console.ReadLine();
            }
        }
    

答案 9 :(得分:0)

Delegate是一个引用类型变量,用于指向对方法的引用。所有委托都派生自System.Delegate类。例如,在Windows窗体或WPF中,方法事件与委托的概念一起使用。 这是在C#Introduction to delegates in C#

中使用delagates的示例