请解释.NET委托

11

我阅读了MSDN和Stack Overflow。我大致理解Action Delegate的作用,但无论我做多少个例子,它都不是那么顺畅。对于代理的概念也是如此。所以这是我的问题。当你有一个像这样的函数:

public GetCustomers(Action<IEnumerable<Customer>,Exception> callBack)
{
}

这是什么,我应该传递什么给它?

9个回答

9
它期望一个接受IEnumerable和Exception并返回void的函数。
void SendExceptionToCustomers(IEnumerable<Customer> customers, Exception ex) {
   foreach(var customer in customers)
      customer.SendMessage(ex.Message);
}

GetCustomers(SendExceptionToCustomers);

顺便提一下,GetCustomers 这个函数名看起来很糟糕——它要求执行一个动作,所以更像是 DoSomethingToCustomers。

编辑 回复评论


好的,这很有道理。那么为什么还要使用 GetCustomer 函数呢?如果我只是将其重命名为 GetCustomer,难道不能实现同样的功能吗?

嗯,这里发生的情况是调用者可以指定某些操作。假设 GetCustomers 是这样实现的:

public void GetCustomers(Action<Enumerable<Customer>, Exception> handleError) {
    Customer[] customerlist =  GetCustomersFromDatabase();
    try {
        foreach(var c in customerList) 
            c.ProcessSomething()
    } catch (Exception e) {
        handleError(customerList, e);
    }
}

那么,您可以在命令行程序的某个地方调用Getcustomers,并将其传递。
GetCustomers((list, exception) => { 
    Console.WriteLine("Encountered error processing the following customers");
    foreach(var customer in list) Console.WriteLine(customer.Name);
    Console.WriteLine(exception.Message);
}); 

尽管您可以从远程应用程序中调用GetCustomers并传递参数,

Getcustomers((list, exception) => { 
    // code that emails me the exception message and customer list
})

此外,Slak的评论指出了委托参数的另一个原因——GetCustomers是异步检索客户信息的。当它完成检索时,会使用您提供的函数来返回客户列表或异常(如果出现异常)。



听起来回调函数会在异步加载顾客之后接收已加载的顾客(或异常)。 - SLaks
2
大多数时候,我很愿意为客户做些事情,尤其是当他们很烦人的时候。 - John K
1
好的,明白了。那现在为什么还要使用 GetCustomer 函数呢?如果我只是将你的函数重命名为 GetCustomer,那不是也能做同样的事情吗? - Mike Diaz
GetCustomers 函数在后台加载客户端,然后在完成时调用回调函数并将已加载的客户端传递给它。 - SLaks
@Slaks,哦,这很有道理,如果将客户列表或异常传递给回调函数。如果它是异步数据库调用,它必须执行类似的操作。 - Jimmy

3
代表是指向一个或多个函数的类。 代表实例可以被调用,这将调用它所指向的函数。 在您的情况下,GetCustomers函数接受第二个函数作为参数。 第二个函数必须采用两个类型为IEnumerable和Exception的参数。 要调用GetCustomers,您需要为其创建一个第二个函数,然后传递一个包含第二个函数的代表。 例如:
static void GetCustomersCallback(IEnumerable<Customer> customers, Exception ex) {
    //Do something...
}

//Elsewhere:
GetCustomers(new Action<IEnumerable<Customer>,Exception>(GetCustomersCallback));

这个调用会创建一个新的委托实例,指向GetCustomersCallback函数,并将该委托传递给GetCustomers函数。预计在客户加载完成后,GetCustomers将调用回调函数,并将已加载的客户作为参数传递。

您还可以省略委托实例化并直接传递函数:

GetCustomers(GetCustomersCallback);

我认为它不是一个类,而是一种独立的引用类型:http://msdn.microsoft.com/en-us/library/900fyy8e(VS.71).aspx - Nick Miller
@Nick:System.Delegate是一个类;你可以在Reflector中看到它。 - SLaks

2

你可以使用lambda表达式来调用它

GetCustomers((cust, ex) => {
    //do something here}
);

2

@Kees -- 是的!看起来URL已经改变。感谢你发现了这个问题。已经更新了。 - PatrickSteele

1

简单易懂?函数指针


1
不仅仅是函数指针。它是一个函数指针和一些数据的组合,这些数据保证适用于该函数的类型。 - supercat

1

这只是C语言函数指针的更新版本,如果它是非静态对象方法指针,则可以将其绑定到对象实例(C++在添加对象和函数指针时称之为方法指针)。

使用C#的泛型功能可以使类型签名通用化。

所有泛型内容都只是对签名进行模板化。

名称委托选择不当(除非您认为我们所有的应用程序都由框架驱动)。因为使用并不总是“委托”。往往是您已经委派某些责任的代码(例如迭代器)调用您先前发送的“委托”。这就是为什么您经常看到术语回调的原因。

传统上,在框架中使用回调来调用您的应用程序代码,以便在循环中间或发生特定事件时调用。它允许您在其他代码的中间发生您的代码-实际上,这几乎是线程内唯一的方法。

显然,.NET事件处理程序是由框架在适当的时间调用的委托,并且您可以在函数中接受委托并适当地调用它们,以使您的代码有些通用/可重用/有组织。


0
你需要传递一个接受IEnumerable和Exception作为参数的void方法...
比如说你有这个方法:
public void DoSomeStuffWithCustomers(
  IEnumerable<Customer> customers, Exception exception)
{     
}

你可以这样调用 GetCustomers 方法:
GetCustomers(DoSomeStuffWithCustomers);

0

在我阅读以下内容之前,它们让我感到非常困惑:

  1. Pro C# 2008 and the .Net 3.5 Platform中Andrew Troelsen对它们的解释
  2. Head First Design Patterns中关于观察者模式的章节

第二本书是关于Java的,并没有提到委托,但它很好地解释了委托有助于解决的问题:类之间的通信。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接