您的位置:首页 > 其它

看看MSDN上是怎么说委托的!它没有什么神秘的,说它神秘是因为你没有仔细看去...

2011-05-20 18:02 267 查看
委托和接口都允许类设计器分离类型声明和实现。任何类或结构都能继承和实现给定的接口。可以为任何类上的方法创建委托,前提是该方法符合委托的方法签名。接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?

在以下情况下,请使用委托:

当使用事件设计模式时。

当封装静态方法可取时。

当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。

需要方便的组合。

当类可能需要该方法的多个实现时。

在以下情况下,请使用接口:

当存在一组可能被调用的相关方法时。

当类只需要方法的单个实现时。

当使用接口的类想要将该接口强制转换为其他接口或类类型时。

当正在实现的方法链接到类的类型或标识时:例如比较方法。

IComparable或泛型版本IComparable<T>就是一个使用单一方法接口而不使用委托的很好的示例。IComparable声明CompareTo方法,该方法返回一个整数,指定相同类型的两个对象之间的小于、等于或大于关系。IComparable可用作排序算法的基础。虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的。

委托是一种安全地封装方法的类型,它与C和C++中的函数指针类似。与C中的函数指针不同,委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。下面的示例声明了一个名为Del的委托,该委托可以封装一个采用字符串作为参数并返回void的方法。

publicdelegatevoidDel(stringmessage);

构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如:

//Createamethodforadelegate.publicstaticvoidDelegateMethod(stringmessage)
{
System.Console.WriteLine(message);
}


//Instantiatethedelegate.
Delhandler=DelegateMethod;

//Callthedelegate.
handler("HelloWorld");

委托类型派生自.NETFramework中的Delegate类。委托类型是密封的,不能从Delegate中派生委托类型,也不可能从中派生自定义类。由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。有关更多信息,请参见何时使用委托而不使用接口。

回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。下面的示例方法使用Del类型作为参数:

publicvoidMethodWithCallback(intparam1,intparam2,Delcallback)
{
callback("Thenumberis:"+(param1+param2).ToString());
}

然后可以将上面创建的委托传递给该方法:

MethodWithCallback(1,2,handler);

在控制台中将收到下面的输出:

Thenumberis:3

在将委托用作抽象概念时,MethodWithCallback不需要直接调用控制台--设计它时无需考虑控制台。MethodWithCallback的作用只是准备字符串并将该字符串传递给其他方法。此功能特别强大,因为委托的方法可以使用任意数量的参数。

将委托构造为包装实例方法时,该委托将同时引用实例和方法。除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。将委托构造为包装静态方法时,它只引用方法。考虑下列声明:

publicclassMethodClass
{
publicvoidMethod1(stringmessage){}
publicvoidMethod2(stringmessage){}
}

加上前面显示的静态DelegateMethod,现在我们有三个方法可由Del实例进行包装。

调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。例如:

MethodClassobj=newMethodClass();
Deld1=obj.Method1;
Deld2=obj.Method2;
Deld3=DelegateMethod;

//Bothtypesofassignmentarevalid.
DelallMethodsDelegate=d1+d2;
allMethodsDelegate+=d3;

此时,allMethodsDelegate在其调用列表中包含三个方法--Method1、Method2和DelegateMethod。原来的三个委托d1、d2和d3保持不变。调用allMethodsDelegate时,将按顺序调用所有这三个方法。如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。若要从调用列表中移除方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。例如:

//removeMethod1
allMethodsDelegate-=d1;

//copyAllMethodsDelegatewhileremovingd2
DeloneMethodDelegate=allMethodsDelegate-d2;

由于委托类型派生自System.Delegate,所以可在委托上调用该类定义的方法和属性。例如,为了找出委托的调用列表中的方法数,您可以编写下面的代码:

intinvocationCount=d1.GetInvocationList().GetLength(0);

在调用列表中具有多个方法的委托派生自MulticastDelegate,这是System.Delegate的子类。由于两个类都支持GetInvocationList,所以上面的代码在两种情况下都适用。

多路广播委托广泛用于事件处理中。事件源对象向已注册接收该事件的接收方对象发送事件通知。为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。事件发生时,源将调用委托。然后,委托调用接收方的事件处理方法并传送事件数据。给定事件的委托类型由事件源定义。有关更多信息,请参见事件(C#编程指南)。

在编译时,对分配了两种不同类型的委托进行比较将产生编译错误。如果委托实例静态地属于类型System.Delegate,则允许进行比较,但在运行时将返回false。例如:

delegatevoidDelegate1();
delegatevoidDelegate2();

staticvoidmethod(Delegate1d,Delegate2e,System.Delegatef)
{
//Compile-timeerror.
//Console.WriteLine(d==e);

//OKatcompile-time.Falseiftherun-timetypeoff
//isnotthesameasthatofd.
System.Console.WriteLine(d==f);
}



匿名方法(C#编程指南)

VisualStudio2010

其他版本

在2.0之前的C#版本中,声明委托的唯一方法是使用命名方法。C#2.0引入了匿名方法,而在C#3.0及更高版本中,Lambda表达式取代了匿名方法,作为编写内联代码的首选方式。不过,本主题中有关匿名方法的信息同样也适用于Lambda表达式。有一种情况下,匿名方法提供了Lambda表达式中所没有的功能。您可使用匿名方法来忽略参数列表。这意味着匿名方法可转换为具有各种签名的委托。这对于Lambda表达式来说是不可能的。有关lambda表达式的更多特定信息,请参见Lambda表达式(C#编程指南)。

要将代码块传递为委托参数,创建匿名方法则是唯一的方法。这里是两个示例:

//Createahandlerforaclickevent.
button1.Click+=delegate(System.Objecto,System.EventArgse)
{System.Windows.Forms.MessageBox.Show("Click!");};

如何:合并委托(多路广播委托)(C#编程指南)

VisualStudio2010

其他版本

更新:2010年9月

此示例演示如何创建多路广播的委托。委派对象的一个有用的属性时多个对象可分配给一个委托实例通过使用+运算符。多路广播的委托包含指定的委托的列表。在调用多路广播的委托时它将调用该委托将按顺序的列表中。只有相同类型的委托可以进行组合。

-运算符可用于移除组件委托从多路广播委托。



示例

usingSystem;

//Defineacustomdelegatethathasastringparameterandreturnsvoid.
delegatevoidCustomDel(strings);

classTestClass
{
//DefinetwomethodsthathavethesamesignatureasCustomDel.
staticvoidHello(strings)
{
System.Console.WriteLine("Hello,{0}!",s);
}

staticvoidGoodbye(strings)
{
System.Console.WriteLine("Goodbye,{0}!",s);
}

staticvoidMain()
{
//Declareinstancesofthecustomdelegate.
CustomDelhiDel,byeDel,multiDel,multiMinusHiDel;

//Inthisexample,youcanomitthecustomdelegateifyou
//wanttoanduseAction<string>instead.
//Action<string>hiDel,byeDel,multiDel,multiMinusHiDel;

//CreatethedelegateobjecthiDelthatreferencesthe
//methodHello.
hiDel=Hello;

//CreatethedelegateobjectbyeDelthatreferencesthe
//methodGoodbye.
byeDel=Goodbye;

//Thetwodelegates,hiDelandbyeDel,arecombinedto
//formmultiDel.
multiDel=hiDel+byeDel;

//RemovehiDelfromthemulticastdelegate,leavingbyeDel,
//whichcallsonlythemethodGoodbye.
multiMinusHiDel=multiDel-hiDel;

Console.WriteLine("InvokingdelegatehiDel:");
hiDel("A");
Console.WriteLine("InvokingdelegatebyeDel:");
byeDel("B");
Console.WriteLine("InvokingdelegatemultiDel:");
multiDel("C");
Console.WriteLine("InvokingdelegatemultiMinusHiDel:");
multiMinusHiDel("D");
}
}
/*Output:
InvokingdelegatehiDel:
Hello,A!
InvokingdelegatebyeDel:
Goodbye,B!
InvokingdelegatemultiDel:
Hello,C!
Goodbye,C!
InvokingdelegatemultiMinusHiDel:
Goodbye,D!
*/


何:声明、实例化和使用委托(C#编程指南)

VisualStudio2010

其他版本

更新:2010年7月

在C#1.0和以后,下面的示例所示,可以声明委托。

//Declareadelegate.delegatevoidDel(stringstr);

//Declareamethodwiththesamesignatureasthedelegate.staticvoidNotify(stringname)
{
Console.WriteLine("Notificationreceivedfor:{0}",name);
}

//Createaninstanceofthedelegate.
Deldel1=newDel(Notify);

C#2.0提供了更简单的方法来编写在以前的声明,如下面的示例所示。

//C#2.0providesasimplerwaytodeclareaninstanceofDel.
Deldel2=Notify;


在C#2.0和更高版本中,也可能是到下面的示例所示声明并初始化一个委派,使用匿名方法。

//InstantiateDelbyusingananonymousmethod.
Deldel3=delegate(stringname)
{Console.WriteLine("Notificationreceivedfor:{0}",name);};

在C#3.0和更高版本,委托可还声明和实例化使用一个lambda表达式,如下面的示例所示。

//InstantiateDelbyusingalambdaexpression.
Deldel4=name=>{Console.WriteLine("Notificationreceivedfor:{0}",name);};


有关更多信息,请参见Lambda表达式(C#编程指南)。

下面的示例阐释声明、实例化和使用委托。BookDB类封装一个书店数据库,它维护一个书籍数据库。它公开ProcessPaperbackBooks方法,该方法在数据库中查找所有平装书,并对每本平装书调用一个委托。使用的delegate类型名为ProcessBookDelegate。Test类使用该类打印平装书的书名和平均价格。

委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书执行什么处理。



示例

//Asetofclassesforhandlingabookstore:
namespaceBookstore
{
usingSystem.Collections;

//Describesabookinthebooklist:
publicstructBook
{
publicstringTitle;//Titleofthebook.
publicstringAuthor;//Authorofthebook.
publicdecimalPrice;//Priceofthebook.
publicboolPaperback;//Isitpaperback?

publicBook(stringtitle,stringauthor,decimalprice,boolpaperBack)
{
Title=title;
Author=author;
Price=price;
Paperback=paperBack;
}
}

//Declareadelegatetypeforprocessingabook:
publicdelegatevoidProcessBookDelegate(Bookbook);

//Maintainsabookdatabase.
publicclassBookDB
{
//Listofallbooksinthedatabase:
ArrayListlist=newArrayList();

//Addabooktothedatabase:
publicvoidAddBook(stringtitle,stringauthor,decimalprice,boolpaperBack)
{
list.Add(newBook(title,author,price,paperBack));
}

//Callapassed-indelegateoneachpaperbackbooktoprocessit:
publicvoidProcessPaperbackBooks(ProcessBookDelegateprocessBook)
{
foreach(Bookbinlist)
{
if(b.Paperback)
//Callingthedelegate:
processBook(b);
}
}
}
}

//UsingtheBookstoreclasses:
namespaceBookTestClient
{
usingBookstore;

//Classtototalandaveragepricesofbooks:
classPriceTotaller
{
intcountBooks=0;
decimalpriceBooks=0.0m;

internalvoidAddBookToTotal(Bookbook)
{
countBooks+=1;
priceBooks+=book.Price;
}

internaldecimalAveragePrice()
{
returnpriceBooks/countBooks;
}
}

//Classtotestthebookdatabase:
classTestBookDB
{
//Printthetitleofthebook.
staticvoidPrintTitle(Bookb)
{
System.Console.WriteLine("{0}",b.Title);
}

//Executionstartshere.
staticvoidMain()
{
BookDBbookDB=newBookDB();

//Initializethedatabasewithsomebooks:
AddBooks(bookDB);

//Printallthetitlesofpaperbacks:
System.Console.WriteLine("PaperbackBookTitles:");

//Createanewdelegateobjectassociatedwiththestatic
//methodTest.PrintTitle:
bookDB.ProcessPaperbackBooks(PrintTitle);

//Gettheaveragepriceofapaperbackbyusing
//aPriceTotallerobject:
PriceTotallertotaller=newPriceTotaller();

//Createanewdelegateobjectassociatedwiththenonstatic
//methodAddBookToTotalontheobjecttotaller:
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

System.Console.WriteLine("AveragePaperbackBookPrice:${0:#.##}",
totaller.AveragePrice());
}

//Initializethebookdatabasewithsometestbooks:
staticvoidAddBooks(BookDBbookDB)
{
bookDB.AddBook("TheCProgrammingLanguage","BrianW.KernighanandDennisM.Ritchie",19.95m,true);
bookDB.AddBook("TheUnicodeStandard2.0","TheUnicodeConsortium",39.95m,true);
bookDB.AddBook("TheMS-DOSEncyclopedia","RayDuncan",129.95m,false);
bookDB.AddBook("Dogbert'sCluesfortheClueless","ScottAdams",12.00m,true);
}
}
}
/*Output:
PaperbackBookTitles:
TheCProgrammingLanguage
TheUnicodeStandard2.0
Dogbert'sCluesfortheClueless
AveragePaperbackBookPrice:$23.97
*/



可靠编程

声明委托。

下面的语句声明一个新的委托类型。

publicdelegatevoidProcessBookDelegate(Bookbook);

每个委托类型都描述参数的数目和类型,以及它可以封装的方法的返回值类型。每当需要一组新的参数类型或新的返回值类型时,都必须声明一个新的委托类型。

实例化委托。

声明了委托类型后,必须创建委托对象并使之与特定方法关联。在上一个示例中,您通过按下面示例中的方式将PrintTitle方法传递到ProcessPaperbackBooks方法来实现这一点:

bookDB.ProcessPaperbackBooks(PrintTitle);

这将创建与静态方法Test.PrintTitle关联的新委托对象。类似地,对象totaller的非静态方法AddBookToTotal是按下面示例中的方式传递的:

bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

在两个示例中,都向ProcessPaperbackBooks方法传递了一个新的委托对象。

委托创建后,它的关联方法就不能更改;委托对象是不可变的。

调用委托。

创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。下面是委托调用的示例:

processBook(b);

与本例一样,可以通过使用BeginInvokeEndInvoke方法同步或异步调用委托。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐