您的位置:首页 > 编程语言 > C#

委托(C# 编程指南)

2011-11-19 22:41 330 查看

委托(C#编程指南)

委托是一种定义方法签名的类型。当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联。您可以通过委托实例调用方法。委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法。您可以创建一个自定义方法,当发生特定事件时某个类(例如
Windows控件)就可以调用您的方法。下面的示例演示了一个委托声明:
publicdelegateintPerformCalculation(intx,inty);
与委托的签名(由返回类型和参数组成)匹配的任何可访问类或结构中的任何方法都可以分配给该委托。方法可以是静态方法,也可以是实例方法。这样就可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。只要知道委托的签名,就可以分配您自己的方法。

注意
在方法重载的上下文中,方法的签名不包括返回值。但在委托的上下文中,签名的确包括返回值。换句话说,方法和委托必须具有相同的返回值。
将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。例如,可以比较两个对象方法的引用可以被作为参数传递到排序算法中。由于比较代码在一个单独的过程中,因此可通过更常见的方式写入排序算法。
委托概述委托具有以下特点:委托类似于C++函数指针,但它们是类型安全的。委托允许将方法作为参数进行传递。委托可用于定义回调方法。委托可以链接在一起;例如,可以对一个事件调用多个方法。方法不必与委托签名完全匹配。有关更多信息,请参见在委托中使用变体(C#和Visual
Basic)。C#2.0版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。C#
3.0引入了Lambda表达式,利用它们可以更简练地编写内联代码块。匿名方法和Lambda表达式(在某些上下文中)都可编译为委托类型。这些功能统称为匿名函数。有关
lambda表达式的更多信息,请参见匿名函数(C#编程指南)。

使用委托

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

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

publicstaticvoidDelegateMethod(stringmessage)

{

System.Console.WriteLine(message);

}
//Instantiatethedelegate.Delhandler=DelegateMethod;
//Callthedelegate.handler("HelloWorld");

委托类型派生自.NETFramework中的Delegate类。委托类型是密封的,不能从Delegate中派生委托类型,也不可能从中派生自定义类。由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。有关更多信息,请参见何时使用委托而不使用接口。
回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。下面的示例方法使用Del
类型作为参数:
[code]publicvoidMethodWithCallback(intparam1,intparam2,Delcallback)
{
callback("Thenumberis:"+(param1+param2).ToString());
}
然后可以将上面创建的委托传递给该方法:
MethodWithCallback(1,2,handler);

在控制台中将收到下面的输出:Thenumberis:3在将委托用作抽象概念时,MethodWithCallback不需要直接调用控制台--设计它时无需考虑控制台。MethodWithCallback
的作用只是准备字符串并将该字符串传递给其他方法。此功能特别强大,因为委托的方法可以使用任意数量的参数。将委托构造为包装实例方法时,该委托将同时引用实例和方法。除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。将委托构造为包装静态方法时,它只引用方法。考虑下列声明:
[code]publicclassMethodClass
{
publicvoidMethod1(stringmessage){}
publicvoidMethod2(stringmessage){}
}
加上前面显示的静态DelegateMethod,现在我们有三个方法可由Del实例进行包装。[/code]调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。例如:
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);

}

何时使用委托而不使用接口

委托和接口都允许类设计器分离类型声明和实现。任何类或结构都能继承和实现给定的接口。可以为任何类上的方法创建委托,前提是该方法符合委托的方法签名。接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?
在以下情况下,请使用委托:
当使用事件设计模式时。当封装静态方法可取时。当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。需要方便的组合。当类可能需要该方法的多个实现时。在以下情况下,请使用接口:
当存在一组可能被调用的相关方法时。当类只需要方法的单个实现时。当使用接口的类想要将该接口强制转换为其他接口或类类型时。当正在实现的方法链接到类的类型或标识时:例如比较方法。IComparable或泛型版本IComparable(OfT)就是一个使用单一方法接口而不使用委托的很好的示例。IComparable声明CompareTo方法,该方法返回一个整数,指定相同类型的两个对象之间的小于、等于或大于关系。IComparable可用作排序算法的基础。虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的。

委托中的变体

.NETFramework3.5和VisualStudio2008引入了变体支持,用于在C#和VisualBasic的所有委托中匹配方法签名和委托类型。这意味着,您不仅可以为委托指派具有匹配签名的方法,而且可以指派这样的方法:它们返回与委托类型指定的派生类型相比,派生程度更大的类型(协变),或者接受相比之下,派生程度更小的类型的参数(逆变)。这包括泛型委托和非泛型委托。
例如,请考虑下面的代码,其中涉及两个类和两个委托,泛型和非泛型各一个。
[code]publicclassFirst{}
publicclassSecond:First{}
publicdelegateFirstSampleDelegate(Seconda);
publicdelegateRSampleGenericDelegate<A,R>(Aa);
创建SampleDelegate或SampleGenericDelegate<A,R>(在VisualBasic中为SampleDelegate(OfA,R))类型的委托时,可以为这些委托指派以下任一方法。
//Matchingsignature.
publicstaticFirstASecondRFirst(Secondfirst)
{returnnewFirst();}
//Thereturntypeismorederived.
publicstaticSecondASecondRSecond(Secondsecond)
{returnnewSecond();}
//Theargumenttypeislessderived.
publicstaticFirstAFirstRFirst(Firstfirst)
{returnnewFirst();}
//Thereturntypeismorederived
//andtheargumenttypeislessderived.
publicstaticSecondAFirstRSecond(Firstfirst)
{returnnewSecond();}
下面的代码示例展示了方法签名和委托类型之间的隐式转换。
//Assigningamethodwithamatchingsignature//toanon-genericdelegate.Noconversionisnecessary.SampleDelegatedNonGeneric=ASecondRFirst;//Assigningamethodwithamorederivedreturntype//andlessderivedargumenttypetoanon-genericdelegate.//Theimplicitconversionisused.SampleDelegatedNonGenericConversion=AFirstRSecond;//Assigningamethodwithamatchingsignaturetoagenericdelegate.//Noconversionisnecessary.SampleGenericDelegate<Second,First>dGeneric=ASecondRFirst;//Assigningamethodwithamorederivedreturntype//andlessderivedargumenttypetoagenericdelegate.//Theimplicitconversionisused.SampleGenericDelegate<Second,First>dGenericConversion=AFirstRSecond;有关更多示例,请参见在委托中使用变体(C#和VisualBasic)和对Func和Action泛型委托使用变体(C#和VisualBasic)。
泛型类型参数中的变体在.NETFramework4中,您可以在委托之间进行隐式转换,这样,如果泛型类型参数指定的不同类型按照变体的要求彼此相互继承,则可以将具有这些类型的泛型委托指派给彼此。
若要启用隐式转换,必须使用in或out关键字,将委托中的泛型参数显式声明为协变或逆变。
下面的代码示例演示如何创建具有协变泛型类型参数的委托。
//TypeTisdeclaredcovariantbyusingtheoutkeyword.
publicdelegateTSampleGenericDelegate<outT>();publicstaticvoidTest()
{
SampleGenericDelegate<String>dString=()=>"";//Youcanassigndelegatestoeachother,
//becausethetypeTisdeclaredcovariant.
SampleGenericDelegate<Object>dObject=dString;
}
如果您仅使用变体支持来匹配方法签名和委托类型,且不使用in和out关键字,则可能会发现,有时可以用相同的lambda表达式或方法来实例化多个委托,但无法将一个委托指派给另一个委托。
在下面的代码示例中,虽然String继承Object,但无法将SampleGenericDelegate<String>显式转换为SampleGenericDelegate<Object>(在VisualBasic中为将SampleGenericDelegate(OfString)转换为SampleGenericDelegate(OfObject))。您可以使用out关键字来标记泛型参数T,从而解决此问题。
publicdelegateTSampleGenericDelegate<T>();publicstaticvoidTest(){SampleGenericDelegate<String>dString=()=>"";//YoucanassignthedObjectdelegate//tothesamelambdaexpressionasdStringdelegate//becauseofthevariancesupportfor//matchingmethodsignatureswithdelegatetypes.SampleGenericDelegate<Object>dObject=()=>"";//Thefollowingstatementgeneratesacompilererror//becausethegenerictypeTisnotmarkedascovariant.//SampleGenericDelegate<Object>dObject=dString;}

.NETFramework中具有变体类型参数的泛型委托

.NETFramework4中为若干现有泛型委托中的泛型类型参数引入了变体支持:
System命名空间中的Action委托,例如Action<T>和Action<T1,T2>System命名空间中的Func委托,例如Func<TResult>和Func<T,TResult>Predicate<T>委托Comparison<T>委托Converter<TInput,TOutput>委托有关更多信息和示例,请参见对Func和Action泛型委托使用变体(C#和VisualBasic)。

在泛型委托中声明变体类型参数

如果泛型委托具有协变或逆变泛型类型参数,则可以将该委托称为“变体泛型委托”。
您可以使用out关键字,在泛型委托中将泛型类型参数声明为协变。协变类型只能用作方法返回类型,不能用作方法参数的类型。下面的代码示例演示如何声明协变泛型委托。
publicdelegateRDCovariant<outR>();
您可以使用in关键字,在泛型委托中将泛型类型参数声明为逆变。逆变类型只能用作方法参数的类型,不能用作方法返回类型。下面的代码示例演示如何声明逆变泛型委托。
publicdelegatevoidDContravariant<inA>(Aa);

重要事项
不能将
VisualBasic中的ByRef参数和C#中的ref与out参数标记为变体。
此外还可以在同一接口中同时支持协变和逆变,但需应用于不同的类型参数。下例演示了这种情况。
publicdelegateRDVariant<inA,outR>(Aa);

实例化和调用变体泛型委托

您可以实例化和调用变体委托,就像实例化和调用固定委托一样。在下例中,委托由lambda表达式进行实例化。
DVariant<String,String>dvariant=(Stringstr)=>str+"";
dvariant("test");

组合变体泛型委托

不应该组合变体委托。Combine方法不支持变体委托转换,并且希望委托的类型完全相同。这可能会在使用Combine方法(在C#和VisualBasic中)或使用+运算符(在C#中)组合委托时导致运行时异常,如下面的代码示例所示。
Action<object>actObj=x=>Console.WriteLine("object:{0}",x);
Action<string>actStr=x=>Console.WriteLine("string:{0}",x);
//Allofthefollowingstatementsthrowexceptionsatruntime.
//Action<string>actCombine=actStr+actObj;
//actStr+=actObj;
//Delegate.Combine(actStr,actObj);


涉及值类型和引用类型的泛型类型参数变体问题只有引用类型才支持泛型类型参数的变体。例如,无法将DVariant<int>(在VisualBasic中为DVariant(OfInt))隐式转换为DVariant<Object>或DVaraint<long>(在VisualBasic中为DVariant(OfObject)或DVaraint(OfLong)),因为整数为值类型。
下例说明了值类型不支持泛型类型参数中的变体。
//ThetypeTiscovariant.
publicdelegateTDVariant<outT>();
//ThetypeTisinvariant.
publicdelegateTDInvariant<T>();
publicstaticvoidTest()
{
inti=0;
DInvariant<int>dInt=()=>i;
DVariant<int>dVariantInt=()=>i;
//Allofthefollowingstatementsgenerateacompilererror
//becausetypevarianceingenericparametersisnotsupported
//forvaluetypes,evenifgenerictypeparametersaredeclaredvariant.
//DInvariant<Object>dObject=dInt;
//DInvariant<long>dLong=dInt;
//DVariant<Object>dVariantObject=dVariantInt;
//DVariant<long>dVariantLong=dVariantInt;
}当向委托分配一个方法时,协变和逆变会提供用于使委托类型与方法签名匹配的灵活性。协变允许方法具有的派生返回类型比委托中定义的更多。逆变允许方法具有的派生参数类型比委托类型中的更少。
示例1:协变

说明

本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。由DogsHandler返回的数据类型是Dogs类型,它是由委托中定义的Mammals类型派生的。

代码

classMammals{}
[code]classDogs:Mammals{}
classProgram
{
//Definethedelegate.
publicdelegateMammalsHandlerMethod();
publicstaticMammalsMammalsHandler()
{
returnnull;
}
publicstaticDogsDogsHandler()
{
returnnull;
}
staticvoidTest()
{
HandlerMethodhandlerMammals=MammalsHandler;
//Covarianceenablesthisassignment.
HandlerMethodhandlerDogs=DogsHandler;
}
}


示例2:逆变

说明

本示例演示如何将委托与具有某个类型的参数的方法一起使用,这些参数是委托签名参数类型的基类型。使用逆变,可以使用一个事件处理程序而不是多个单独的处理程序。例如,可以创建接受EventArgs输入参数的事件处理程序,并将其与将MouseEventArgs类型作为参数发送的Button.MouseClick事件一起使用,也可以将其与发送KeyEventArgs参数的TextBox.KeyDown事件一起使用。

代码

//EventhanderthatacceptsaparameteroftheEventArgstype.
privatevoidMultiHandler(objectsender,System.EventArgse)
{
label1.Text=System.DateTime.Now.ToString();
}publicForm1()
{
InitializeComponent();
//YoucanuseamethodthathasanEventArgsparameter,
//althoughtheeventexpectstheKeyEventArgsparameter.
this.button1.KeyDown+=this.MultiHandler;//Youcanusethesamemethod
//foraneventthatexpectstheMouseEventArgsparameter.
this.button1.MouseClick+=this.MultiHandler;
}

Lambda表达式

VisualStudio2010“Lambda表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。
所有Lambda表达式都使用Lambda运算符=>,该运算符读为“goesto”。该Lambda运算符的左边是输入参数(如果有),右边包含表达式或语句块。Lambda表达式x=>x*x读作“xgoestoxtimesx”。可以将此表达式分配给委托类型,如下所示:
delegateintdel(inti);
staticvoidMain(string[]args)
{
delmyDelegate=x=>x*x;
intj=myDelegate(5);//j=25
}
创建表达式树类型:
usingSystem.Linq.Expressions;
namespaceConsoleApplication1
{
classProgram
{
staticvoidMain(string[]args)
{
Expression<del>myET=x=>x*x;
}
}
}
=>运算符具有与赋值运算符(=)相同的优先级,并且是右结合运算符。
Lambda在基于方法的LINQ查询中用作标准查询运算符方法(如Where)的参数。
使用基于方法的语法在Enumerable类中调用Where方法时(像在LINQtoObjects和LINQtoXML中那样),参数是委托类型System.Func<T,TResult>。使用Lambda表达式创建委托最为方便。例如,当您在System.Linq.Queryable类中调用相同的方法时(像在LINQtoSQL中那样),则参数类型是System.Linq.Expressions.Expression<Func>,其中Func是包含至多十六个输入参数的任何Func委托。同样,Lambda表达式只是一种用于构造表达式树的非常简练的方式。尽管事实上通过Lambda创建的对象的类型是不同的,但Lambda使得Where调用看起来类似。
在前面的示例中,请注意委托签名具有一个int类型的隐式类型输入参数,并返回int。可以将Lambda表达式转换为该类型的委托,因为该表达式也具有一个输入参数(x),以及一个编译器可隐式转换为int类型的返回值。(以下几节中将对类型推理进行详细讨论。)使用输入参数5调用委托时,它将返回结果25。
is或as运算符的左侧不允许使用Lambda。
适用于匿名方法的所有限制也适用于Lambda表达式。有关更多信息,请参见匿名方法(C#编程指南)。
表达式在右边的Lambda表达式称为“Lambda表达式”。Lambda表达式在构造表达式树(C#和VisualBasic)时广泛使用。Lambda表达式返回表达式的结果,并采用以下基本形式:
(inputparameters)=>expression
只有在Lambda有一个输入参数时,括号才是可选的;否则括号是必需的。两个或更多输入参数由括在括号中的逗号分隔:
(x,y)=>x==y
有时,编译器难于或无法推断输入类型。如果出现这种情况,您可以按以下示例中所示方式显式指定类型:
(intx,strings)=>s.Length>x
使用空括号指定零个输入参数:
()=>SomeMethod()
在上一个示例中,请注意Lambda表达式的主体可以包含方法调用。但是,如果要创建将在另一个域(比如SQLServer)中使用的表达式树,则不应在Lambda表达式中使用方法调用。方法在.NET公共语言运行时上下文的外部将没有意义。


Lambda语句Lambda语句与Lambda表达式类似,只是语句括在大括号中:
(inputparameters)=>{statement;}
Lambda语句的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个语句。
delegatevoidTestDelegate(strings);
TestDelegatemyDel=n=>{strings=n+""+"World";Console.WriteLine(s);};
myDel("Hello");
像匿名方法一样,Lambda语句无法用于创建表达式树。
带有标准查询运算符的Lambda许多标准查询运算符都具有输入参数,其类型是泛型委托的Func<T,TResult>系列的其中之一。Func<T,TResult>委托使用类型参数定义输入参数的数目和类型,以及委托的返回类型。Func委托对于封装应用于一组源数据中每个元素的用户定义表达式非常有用。例如,假设有以下委托类型:
publicdelegateTResultFunc<TArg0,TResult>(TArg0arg0)
可以将委托实例化为Func<int,bool>myFunc,其中int是输入参数,bool是返回值。始终在最后一个类型参数中指定返回值。Func<int,string,bool>定义包含两个输入参数(int和string)且返回类型为bool的委托。在调用下面的Func委托时,该委托将返回true或false以指示输入参数是否等于5:
Func<int,bool>myFunc=x=>x==5;boolresult=myFunc(4);//returnsfalseofcourse当参数类型为Expression<Func>时,您也可以提供Lambda表达式,例如在System.Linq.Queryable内定义的标准查询运算符中。如果指定Expression<Func>参数,Lambda将编译为表达式树。
此处显示了一个标准查询运算符,Count方法:
int[]numbers={5,4,1,3,9,8,6,7,2,0};
intoddNumbers=numbers.Count(n=>n%2==1);
编译器可以推断输入参数的类型,或者您也可以显式指定该类型。这个特别的Lambda表达式将计算整数(n)的数量,这些整数除以2时余数为1。
以下方法将生成一个序列,其中包含numbers数组中在9左边的所有元素,因为9是序列中不满足条件的第一个数字:
varfirstNumbersLessThan6=numbers.TakeWhile(n=>n<6);
此示例演示如何通过将输入参数括在括号中来指定多个输入参数。该方法将返回数字数组中的所有元素,直至遇到一个值小于其位置的数字为止。不要将Lambda运算符(=>)与大于等于运算符(>=)混淆。
varfirstSmallNumbers=numbers.TakeWhile((n,index)=>n>=index);


Lambda中的类型推理在编写Lambda时,通常不必为输入参数指定类型,因为编译器可以根据Lambda主体、基础委托类型以及C#语言规范中描述的其他因素推断类型。对于大多数标准查询运算符,第一个输入是源序列中的元素的类型。因此,如果要查询IEnumerable<Customer>,则输入变量将被推断为Customer对象,这意味着您可以访问其方法和属性:
customers.Where(c=>c.City=="London");
Lambda的一般规则如下:
Lambda包含的参数数量必须与委托类型包含的参数数量相同。Lambda中的每个输入参数必须都能够隐式转换为其对应的委托参数。Lambda的返回值(如果有)必须能够隐式转换为委托的返回类型。请注意,Lambda表达式本身没有类型,因为常规类型系统没有“Lambda表达式”这一内部概念。但是,有时会不正式地论及Lambda表达式的“类型”。在这些情况下,类型是指委托类型或Lambda表达式所转换为的Expression类型。
Lambda表达式中的变量范围Lambda可以引用“外部变量”,这些变量位于在其中定义Lambda的封闭方法或类型的范围内。将会存储通过这种方法捕获的变量以供在Lambda表达式中使用,即使变量将以其他方式超出范围或被作为垃圾回收。必须明确地分配外部变量,然后才能在Lambda表达式中使用该变量。下面的示例演示这些规则:
delegateboolD();
delegateboolD2(inti);
classTest
{
Ddel;
D2del2;
publicvoidTestMethod(intinput)
{
intj=0;
//Initializethedelegateswithlambdaexpressions.
//Noteaccessto2outervariables.
//delwillbeinvokedwithinthismethod.
del=()=>{j=10;returnj>input;};
//del2willbeinvokedafterTestMethodgoesoutofscope.
del2=(x)=>{returnx==j;};

//Demonstratevalueofj:
//Output:j=0
//Thedelegatehasnotbeeninvokedyet.
Console.WriteLine("j={0}",j);//Invokethedelegate.
boolboolResult=del();
//Output:j=10b=True
Console.WriteLine("j={0}.b={1}",j,boolResult);
}
staticvoidMain()
{
Testtest=newTest();
test.TestMethod(5);
//Provethatdel2stillhasacopyof
//localvariablejfromTestMethod.
boolresult=test.del2(10);
//Output:True
Console.WriteLine(result);
Console.ReadKey();
}
}
下列规则适用于Lambda表达式中的变量范围:捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。在外部方法中看不到Lambda表达式内引入的变量。Lambda表达式无法从封闭方法中直接捕获ref或out参数。Lambda表达式中的返回语句不会导致封闭方法返回。Lambda表达式不能包含其目标位于所包含匿名函数主体外部或内部的goto语句、break语句或continue语句。

对Func和Action泛型委托使用变体

下例演示如何在Func和Action泛型委托中使用协变和逆变,以便能够在代码中重用方法及提供更大的灵活性。
有关协变和逆变的更多信息,请参见委托中的变体(C#和VisualBasic)。
下例展示了Func泛型委托中的协变支持带来的益处。FindByTitle方法获取String类型的参数,返回Employee类型的对象。但是,您可以将此方法指派给Func<String,Person>委托(在VisualBasic中为Func(OfString,Person)),因为Employee继承Person。
//Simplehierarchyofclasses.
publicclassPerson{}
publicclassEmployee:Person{}
classProgram
{
staticEmployeeFindByTitle(Stringtitle)
{
//Thisisastubforamethodthatreturns
//anemployeethathasthespecifiedtitle.
returnnewEmployee();
}
staticvoidTest()
{
//Createaninstanceofthedelegatewithoutusingvariance.
Func<String,Employee>findEmployee=FindByTitle;
//ThedelegateexpectsamethodtoreturnPerson,
//butyoucanassignitamethodthatreturnsEmployee.
Func<String,Person>findPerson=FindByTitle;
//Youcanalsoassignadelegate
//thatreturnsamorederivedtype
//toadelegatethatreturnsalessderivedtype.
findPerson=findEmployee;
}
}


使用具有逆变类型参数的委托下例展示了Action泛型委托中的逆变支持带来的益处。AddToContacts方法获取Person类型的参数。但是,您可以将此方法指派给Action<Employee>委托(在VisualBasic中为(Action(OfEmployee)),因为Employee继承Person。
publicclassPerson{}
publicclassEmployee:Person{}
classProgram
{
staticvoidAddToContacts(Personperson)
{
//ThismethodaddsaPersonobject
//toacontactlist.
}
staticvoidTest()
{
//Createaninstanceofthedelegatewithoutusingvariance.
Action<Person>addPersonToContacts=AddToContacts;
//TheActiondelegateexpects
//amethodthathasanEmployeeparameter,
//butyoucanassignitamethodthathasaPersonparameter
//becauseEmployeederivesfromPerson.
Action<Employee>addEmployeeToContacts=AddToContacts;
//Youcanalsoassignadelegate
//thatacceptsalessderivedparametertoadelegate
//thatacceptsamorederivedparameter.
addEmployeeToContacts=addPersonToContacts;
}
}
[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: