我在理解C#中的委托如何工作方面遇到了一些麻烦。我有很多代码示例,但我仍然无法正确掌握它。
有人可以用“普通英语”向我解释一下吗?当然!代码示例会有所帮助,但我认为我需要更多地描述它是如何/为什么有效。
编辑:
嗯,问题是:为什么代表们工作?什么是整个过程的“流程图”?
使用委托的先决条件是什么?
我希望这会使问题更清楚。
答案 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的替代解决方案:
定义 NotifyCustomer()方法,该方法处理在 BankAccount 类之外向客户发送有关低余额的通知。
< / LI>修改 BankAccount 类以定义委托并在构造函数中接受它的值。
创建 BankAccount 类时,请传入步骤1中创建的 NotifyCustomer()方法。
在BankAccount的类Balance setter中,检查余额是否低于100美元。如果是,请调用该委托。
调用 BankAccount 类之外定义的 NotifyCustomer()方法,从而发送定义的通知。
将来,如果有新的方式通知客户,则 BankAccount 类不需要进行任何更改。
使用代表设计的优点:
放松联接: BankAccount 类不知道通知客户的硬编码逻辑。
遵循OPEN FOR EXTENSION和CLOSED FOR MODIFICATION原则:每当通知客户的媒介发生变化时,您无需更改 BankAccount 类。所以现在你会自豪地说你的 BankAccount 课程设计遵循设计原则。
如果您想通过示例了解有关委托的更多信息,请阅读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#中延迟:它定义了它可以调用的方法的签名。换句话说,我们可以说它包装了它可以调用的方法的引用。 以下是 代表的用途:
它支持静态和实例方法。
以下是内部工作原理的解释。
//这是代表的声明。
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的示例