我对c#比较陌生,我想知道什么时候应该适当地使用委托。 它们在事件声明中被广泛使用,但是什么时候应该在我自己的代码中使用它们,为什么它们有用?为什么不用别的东西呢?
我也想知道什么时候我必须使用委托,我没有其他的选择。
谢谢你的帮助!
编辑:我认为我在这里找到了委托的必要用途
我对c#比较陌生,我想知道什么时候应该适当地使用委托。 它们在事件声明中被广泛使用,但是什么时候应该在我自己的代码中使用它们,为什么它们有用?为什么不用别的东西呢?
我也想知道什么时候我必须使用委托,我没有其他的选择。
谢谢你的帮助!
编辑:我认为我在这里找到了委托的必要用途
当你想要声明你想要传递的代码块时,委托是非常有用的。例如,当使用通用重试机制时。
伪:
function Retry(Delegate func, int numberOfTimes)
try
{
func.Invoke();
}
catch { if(numberOfTimes blabla) func.Invoke(); etc. etc. }
或者当你想要做代码块的后期计算,比如你有一些Transform动作的函数,并且想要有一个BeforeTransform和一个AfterTransform动作,你可以在你的Transform函数中计算,而不需要知道BeginTransform是否被填充,或者它必须转换什么。
当然,在创建事件处理程序时也是如此。您不希望现在计算代码,而只希望在需要时计算代码,因此您可以注册一个可以在事件发生时调用的委托。
我同意已经说过的一切,只是试着用另一种说法来解释。
委托可以被视为一个或多个方法的占位符。
通过定义委托,您就在对类的用户说:“请随意将与此签名匹配的任何方法分配给委托,并且每次调用我的委托时都会调用它”。
典型的使用当然是事件。所有的OnEventX委托给用户定义的方法。
委托非常有用,可以为对象的用户提供一些自定义行为的能力。 大多数时候,你可以使用其他方法来达到同样的目的,我不相信你会被强迫创建委托。在某些情况下,这是完成任务最简单的方法。
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.
委托是一个简单的类,用于指向具有特定签名的方法,本质上成为类型安全的函数指针。委托的目的是在一个方法完成后,以结构化的方式方便对另一个(或多个)方法的回调。
虽然可以创建一组大量的代码来执行此功能,但您不需要这样做。你可以使用委托。
创建委托很容易做到。使用"delegate"关键字将类标识为委托。然后指定类型的签名。
假设你想写一个过程,在某个区间[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);
}