在读一本C#的入门书时,终于看到了类,看到了类的事件,书上讲得很简单,看例子却看不懂了——这也难怪,C++基础并不牢靠、完全没有面向对象程序设计思想的我,一下子看到那么多类绞在一起,中间还夹杂着事件、委托这样陌生的概念,自然读得一头雾水。于是去查msdn,说让我先看委托——有过几次经验,比较信任msdn,便先去看msdn,没想到一看就是一下午的功夫。
下面将自己浅显的理解写下来,不期为他人有何启发,只是为了自己小小一步的记录:
引经据典先,msdn中是这样解释委托的:
C# 中的委托类似于 C 或 C++ 中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与 C 或 C++ 中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。
委托声明定义一种类型,它用一组特定的参数以及返回类型封装方法。对于静态方法,委托对象封装要调用的方法。对于实例方法,委托对象同时封装一个实例和该实例上的一个方法。如果您有一个委托对象和一组适当的参数,则可以用这些参数调用该委托。
委托的一个有趣且有用的属性是,它不知道或不关心自己引用的对象的类。任何对象都可以;只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。这使得委托完全适合“匿名”调用。
说实话,至今我对这些话还是没有完全闹明白,现在知道的只是:委托类似于函数指针。下面就这一点,结合msdn给的一个例子,谈谈自己的分析和理解。
例子附后,分析如下:
在本例中,定义了如下几个实体:
结构体Book,表示一本书的信息;
类BookDB,表示书的数据库
类PriceTotaller,用以计算数据库中书的总价和平均价格
类Test,用于测试委托这一概念的主函数所在类
委托ProcessBookDelegate,从其命名上看也知道,用于处理一本书(的价格,当然也可心有更强大的功能)
它们的作用,我觉得全在这一句里面:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
对于我这样没有真正接触过面向对象编程的人来说,读这句真是费劲,我试着把这句话分解:
ProcessBookDelegate pbd= new ProcessBookDelegate;
pbd(totaller.AddBookToTotal);
bookDB.ProcessPaperbackBooks(pbd);
上面是伪代码,三行分别表示三个分解的动作:创建一个委托,将之关联到类Totaller(这里其实是类的实例totaller)的方法AddBookToTotal(),数据库bookDB以委托pbd为参数调用方法ProcessPaperbackBooks()。
数据库bookDB的方法ProcessPaperbackBooks()中简单的调用委托prcessBook,并传递给它一个参数:数据库中的书结构体b。而processBook()是与类Totaller(实为其实例totaller)的方法AddBookToTotal (),程序转而去执行这个方法,并把参数b传递给它。
public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
为什么要这样做?msdn给出了一堆理由:
为什么要这样做?msdn给出了一堆理由:{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
为什么要这样做?msdn给出了一堆理由:
委托在以下情况下很有用:
· 调用单个方法
· 一个类可能希望有方法规范的多个实现。
· 希望允许使用静态方法实现规范。
· 希望类似事件的设计模式(有关更多信息,请参阅事件教程)。
· 调用方不需要知道或获得在其上定义方法的对象。
· 实现的提供程序希望只对少数选择组件“分发”规范实现。
· 需要方便的组合。
和往常一样,我一下子也并没有完全理解,而且本文的目的只是为了说明委托的工作方法。这里也就不再画蛇添足了。
再就msdn中讲到的另外两点谈谈自己的理解:
委托声明定义一种类型,它用一组特定的参数以及返回类型封装方法。对于静态方法,委托对象封装要调用的方法。对于实例方法,委托对象同时封装一个实例和该实例上的一个方法。如果您有一个委托对象和一组适当的参数,则可以用这些参数调用该委托。
上面提到的只是将委托关联到一个方法的一种方法,pbd实际上是关联到了一个具体的类的实例totaller上的AddBookToTotal()方法上。我想,这就是msdn指的“委托对象同时封装一个实例和该实例上的一个方法”。实际上,委托还可以直接关联到某一个方法而不必创建一个具体的实例,如:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
这大概就是文中所指的“对于静态方法,委托对象封装要调用的方法”。其实我觉得这句话这样说更好一点,如果委托对象要直接封装所要调用的方法,则该方法必须为表态方法。
委托的一个有趣且有用的属性是,它不知道或不关心自己引用的对象的类。任何对象都可以;只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。这使得委托完全适合“匿名”调用。
这段话是说,定义了一个委托后,就可以通过这个委托去引用(这里“引用”的概念与C++中不同,非别名机制。我觉得可能说调用比较好一点)其它任何类的方法,而不必管这个类与自己有没有关系。当然,前提是,这个方法的参数和返回类型与委托本身的参数和返回类型相匹配。
源程序:
// bookstore.cs
using System;
// A set of classes for handling a bookstore:
namespace Bookstore
{
using System.Collections;
// Describes a book in the book list:
public struct Book
{
public string Title; // Title of the book.
public string Author; // Author of the book.
public decimal Price; // Price of the book.
public bool Paperback; // Is it paperback?
public Book(string title, string author, decimal price, bool paperBack)
{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}
// Declare a delegate type for processing a book:
public delegate void ProcessBookDelegate(Book book);
// Maintains a book database.
public class BookDB
{
// List of all books in the database:
ArrayList list = new ArrayList();
// Add a book to the database:
public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}
// Call a passed-in delegate on each paperback book to process it:
public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
}
}
// Using the Bookstore classes:
namespace BookTestClient
{
using Bookstore;
// Class to total and average prices of books:
class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m ;
internal void AddBookToTotal(Book book)
{
countBooks += 1;
priceBooks += book.Price;
}
internal decimal AveragePrice()
{
return priceBooks / countBooks;
}
}
// Class to test the book database:
class Test
{
// Print the title of the book.
static void PrintTitle(Book b)
{
Console.WriteLine(" {0}", b.Title);
}
// Execution starts here.
static void Main()
{
BookDB bookDB = new BookDB();
// Initialize the database with some books:
AddBooks(bookDB);
// Print all the titles of paperbacks:
Console.WriteLine("Paperback Book Titles:");
// Create a new delegate object associated with the static
// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
// Get the average price of a paperback by using
// a PriceTotaller object:
PriceTotaller totaller = new PriceTotaller();
// Create a new delegate object associated with the nonstatic
// method AddBookToTotal on the object totaller:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}
// Initialize the book database with some test books:
static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language",
"Brian W. Kernighan and Dennis M. Ritchie", 19.95m , true);
bookDB.AddBook("The Unicode Standard 2.0",
"The Unicode Consortium", 39.95m , true);
bookDB.AddBook("The MS-DOS Encyclopedia",
"Ray Duncan", 129.95m , false);
bookDB.AddBook("Dogbert's Clues for the Clueless",
"Scott Adams", 12.00m , true);
}
}
}
输出
Paperback Book Titles:
The C Programming Language
The Unicode Standard 2.0
Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97